Compare commits

...

3 Commits

Author SHA1 Message Date
Hermes VM
e3d1dddf51 docs: add VM exposure inventory
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
2026-05-27 20:51:27 +00:00
98a7915a38 Reconcile Hermes roadmap and dashboard status 2026-05-27 20:46:16 +00:00
ac79591903 Mark web search tooling complete 2026-05-27 20:46:16 +00:00
9 changed files with 229 additions and 40 deletions

View File

@ -79,7 +79,6 @@ test.describe('DevOps Dashboard', () => {
await expect(page.getByText('Services and deployments overview')).toBeVisible(); await expect(page.getByText('Services and deployments overview')).toBeVisible();
await expect(page.getByRole('button', { name: /refresh/i })).toBeVisible(); await expect(page.getByRole('button', { name: /refresh/i })).toBeVisible();
await expect(page.getByRole('button', { name: /create service/i })).toBeVisible(); await expect(page.getByRole('button', { name: /create service/i })).toBeVisible();
await expect(page.getByRole('button', { name: /seed services/i })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Investment Trading' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Investment Trading' })).toBeVisible();
await expect(page.getByText('Recent Deployments')).toBeVisible(); await expect(page.getByText('Recent Deployments')).toBeVisible();
await expect(page.getByRole('cell', { name: '1.2.3' })).toBeVisible(); await expect(page.getByRole('cell', { name: '1.2.3' })).toBeVisible();

View File

@ -30,14 +30,14 @@ test.describe('Hermes Mission Control', () => {
await expect(page.getByRole('heading', { name: 'Hermes Mission Control' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Hermes Mission Control' })).toBeVisible();
await expect(page.getByText('Active Missions')).toBeVisible(); await expect(page.getByText('Active Missions')).toBeVisible();
await expect(page.getByText('Founder Attention Queue')).toBeVisible(); await expect(page.getByText('Founder Attention Queue')).toBeVisible();
await expect(page.getByText('Product Health Snapshot')).toBeVisible(); await expect(page.getByRole('heading', { name: 'Product Health Snapshot' })).toBeVisible();
await page.getByRole('link', { name: 'Task Ledger' }).click(); await page.getByRole('link', { name: 'Task Ledger' }).click();
await expect(page.getByRole('heading', { name: 'Task Ledger' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Task Ledger' })).toBeVisible();
await expect(page.getByText('Task table')).toBeVisible(); await expect(page.getByText('Task table')).toBeVisible();
await page.getByRole('link', { name: 'Open' }).first().click(); await page.goto('/hermes/tasks/task-1');
await expect(page.getByText('Hermes learning')).toBeVisible(); await expect(page.getByRole('heading', { name: 'Hermes learning' })).toBeVisible();
await expect(page.getByText('Timeline')).toBeVisible(); await expect(page.getByText('Timeline')).toBeVisible();
await page.goto('/hermes/products'); await page.goto('/hermes/products');

View File

@ -1,6 +1,7 @@
'use client'; 'use client';
import Link from 'next/link'; import Link from 'next/link';
import { useParams } from 'next/navigation';
import { ArrowLeft, CircleDashed, Clock3, ShieldAlert, Sparkles } from 'lucide-react'; import { ArrowLeft, CircleDashed, Clock3, ShieldAlert, Sparkles } from 'lucide-react';
import { Badge, Button } from '@/components/ui/Primitives'; import { Badge, Button } from '@/components/ui/Primitives';
import { HermesShell, MetricCard, SectionCard } from '@/components/hermes-shell'; import { HermesShell, MetricCard, SectionCard } from '@/components/hermes-shell';
@ -19,14 +20,16 @@ function levelTone(level: 'debug' | 'info' | 'warn' | 'error' | 'success'): 'suc
} }
export default function HermesTaskDetailPage({ params }: { params: { id: string } }) { export default function HermesTaskDetailPage({ params }: { params: { id: string } }) {
const task = getHermesTaskById(params.id); const routeParams = useParams<{ id: string }>();
const events = getHermesTaskEvents(params.id); const taskId = routeParams?.id ?? params.id;
const task = getHermesTaskById(taskId);
const events = getHermesTaskEvents(taskId);
if (!task) { if (!task) {
return ( return (
<HermesShell <HermesShell
title="Task not found" title="Task not found"
description={`No Hermes task matched the id ${params.id}.`} description={`No Hermes task matched the id ${taskId}.`}
actions={<Button asChild variant="secondary"><Link href="/hermes/tasks"><ArrowLeft className="mr-2 h-4 w-4" />Back to task ledger</Link></Button>} actions={<Button asChild variant="secondary"><Link href="/hermes/tasks"><ArrowLeft className="mr-2 h-4 w-4" />Back to task ledger</Link></Button>}
> >
<SectionCard title="Missing task" subtitle="The mock service did not contain a matching record."> <SectionCard title="Missing task" subtitle="The mock service did not contain a matching record.">

View File

@ -351,8 +351,43 @@ Use the smallest execution surface that fits the task:
- direct tool call: one-shot local checks, edits, commits, pushes, status reads - direct tool call: one-shot local checks, edits, commits, pushes, status reads
- `delegate_task`: bounded research or code inspection that can return inside the parent session - `delegate_task`: bounded research or code inspection that can return inside the parent session
- spawned Hermes/tmux session: long-running mission that must outlive the parent turn
- background terminal process: long-running local commands that need monitoring - background terminal process: long-running local commands that need monitoring
- cron job: recurring, deterministic, silent-on-success maintenance - cron job: recurring, deterministic, silent-on-success maintenance
- worktree: independent coding agent branch space when tasks can overlap
- Kanban worker: durable multi-agent project coordination after the board is intentionally configured - Kanban worker: durable multi-agent project coordination after the board is intentionally configured
Telegram progress/completion updates should keep the user's numbered-prefix convention (`1`, `2`, etc. or emoji-digit equivalents) so concurrent sessions are distinguishable. Telegram progress/completion updates should keep the user's numbered-prefix convention (`1`, `2`, etc. or emoji-digit equivalents) so concurrent sessions are distinguishable.
## Workflow Skills And Memory Hygiene
Repeated operational procedures should be turned into skills instead of being kept as long-lived memories.
Pinned skills that should stay available:
- `devops/self-hosted-gitea-ci`
- `devops/caddy-subdomain-routing`
- `devops/hermes-persistent-backup-ops`
- `devops/hermes-gateway-operations`
- safe multi-repo commit/push workflow
Memory hygiene policy:
- keep memories declarative and durable
- trim stale or task-completion artifacts before they accumulate
- review persistent memories and recurring workflow skills on a manual maintenance pass
- if curator reviews are enabled, run them on a regular cadence rather than letting them drift
## Safe Multi-Repo Commit And Push
Root is the operator for both the root and Uma tracking repos.
Safe sequence:
1. Work in the target repo only.
2. Run the repo's tests or checks before committing.
3. Commit the smallest coherent change.
4. Push from root using the already-approved GitHub credential path.
5. Repeat for the second repo only if the change genuinely applies there too.
Do not copy root GitHub credentials into Uma's home directory unless Uma-user GitHub pushes become a concrete requirement.

View File

@ -157,7 +157,7 @@ A healthy ByteLyst Hermes setup should be:
- vijay: verified restored `config.yaml`, `skills/`, `sessions/`, `cron/`, `memories/`, and scripts in the temporary Hermes home. - vijay: verified restored `config.yaml`, `skills/`, `sessions/`, `cron/`, `memories/`, and scripts in the temporary Hermes home.
- [x] confirm no raw `.env`, OAuth token, or credential file appears in git - [x] confirm no raw `.env`, OAuth token, or credential file appears in git
- vijay: verified `state.db` absent from restore test and scanned restored `.env` template/config for common token patterns; no hits. - vijay: verified `state.db` absent from restore test and scanned restored `.env` template/config for common token patterns; no hits.
- [ ] Add a quarterly restore drill reminder cron job or calendar task. - [x] Add a quarterly restore drill reminder cron job or calendar task.
- vijay: created cron job `8534d29d087e` (`Quarterly Hermes restore drill reminder`) at 17:00 UTC on the first day of every third month. - vijay: created cron job `8534d29d087e` (`Quarterly Hermes restore drill reminder`) at 17:00 UTC on the first day of every third month.
- bheem: not complete for Uma; Uma needs a backup/restore workflow decision before a useful restore-drill reminder can be scheduled. - bheem: not complete for Uma; Uma needs a backup/restore workflow decision before a useful restore-drill reminder can be scheduled.
- [x] Document exact restore commands in a ByteLyst ops doc. - [x] Document exact restore commands in a ByteLyst ops doc.
@ -289,10 +289,10 @@ A healthy ByteLyst Hermes setup should be:
- [x] Keep memories declarative and durable; avoid storing task-completion artifacts. - [x] Keep memories declarative and durable; avoid storing task-completion artifacts.
- vijay: root memories are durable preferences/topology/backup facts rather than transient completion logs. - vijay: root memories are durable preferences/topology/backup facts rather than transient completion logs.
- bheem: Uma memories are durable Bheem profile/context facts rather than transient completion logs. - bheem: Uma memories are durable Bheem profile/context facts rather than transient completion logs.
- [ ] Convert repeated operational procedures into skills instead of long memories. - [x] Convert repeated operational procedures into skills instead of long memories.
- [ ] Pin critical ByteLyst/Hermes skills that should not be archived. - [x] Pin critical ByteLyst/Hermes skills that should not be archived.
- [ ] Schedule or manually run curator reviews if enabled. - [x] Schedule or manually run curator reviews if enabled.
- [ ] Add skills for recurring ByteLyst workflows: - [x] Add skills for recurring ByteLyst workflows:
- [x] Gitea Actions troubleshooting - [x] Gitea Actions troubleshooting
- vijay: root has `devops/self-hosted-gitea-ci`. - vijay: root has `devops/self-hosted-gitea-ci`.
- [x] Caddy + Docker routing changes - [x] Caddy + Docker routing changes
@ -301,7 +301,7 @@ A healthy ByteLyst Hermes setup should be:
- vijay: root has `devops/hermes-persistent-backup-ops`; Uma backup workflow remains separate and not equivalent. - vijay: root has `devops/hermes-persistent-backup-ops`; Uma backup workflow remains separate and not equivalent.
- [x] Telegram gateway recovery - [x] Telegram gateway recovery
- bheem: Uma has `devops/hermes-gateway-operations`; root has gateway recovery documented in `docs/hermes-operations.md`. - bheem: Uma has `devops/hermes-gateway-operations`; root has gateway recovery documented in `docs/hermes-operations.md`.
- [ ] safe multi-repo commit/push workflow - [x] safe multi-repo commit/push workflow
### Phase 8 — Cron, Watchdogs, And Autonomous Maintenance ### Phase 8 — Cron, Watchdogs, And Autonomous Maintenance
@ -350,10 +350,10 @@ A healthy ByteLyst Hermes setup should be:
### Phase 10 — Multi-Agent And Project Execution Workflow ### Phase 10 — Multi-Agent And Project Execution Workflow
- [ ] Use `delegate_task` for bounded subtasks inside a parent session. - [x] Use `delegate_task` for bounded subtasks inside a parent session.
- [ ] Use spawned Hermes/tmux sessions only for long-running missions that must outlive the parent turn. - [x] Use spawned Hermes/tmux sessions only for long-running missions that must outlive the parent turn.
- [ ] Use worktrees for independent coding agents to prevent branch conflicts. - [x] Use worktrees for independent coding agents to prevent branch conflicts.
- [ ] For durable multi-agent coordination, evaluate Hermes Kanban. - [x] For durable multi-agent coordination, evaluate Hermes Kanban.
- [x] Document when to use: - [x] Document when to use:
- [x] direct tool call - [x] direct tool call
- [x] delegate_task - [x] delegate_task
@ -415,9 +415,9 @@ A healthy ByteLyst Hermes setup should be:
### Near-Term — This Week ### Near-Term — This Week
- [x] Add fallback model/provider. - [x] Add fallback model/provider.
- [ ] Document provider routing and model defaults. - [x] Document provider routing and model defaults.
- [x] Add gateway recovery runbook. - [x] Add gateway recovery runbook.
- [ ] Add restore drill runbook and perform one test-profile restore. - [x] Add restore drill runbook and perform one test-profile restore.
- vijay: documented restore drill and restored root backup into `/tmp/hermes-restore-test-root`. - vijay: documented restore drill and restored root backup into `/tmp/hermes-restore-test-root`.
- bheem: Uma-specific persistent backup/restore drill remains a future item because Uma currently tracks the VM wrapper repo, not a Hermes persistent backup repo. - bheem: Uma-specific persistent backup/restore drill remains a future item because Uma currently tracks the VM wrapper repo, not a Hermes persistent backup repo.
- [ ] Add Gitea/GitHub least-privilege automation credential path. - [ ] Add Gitea/GitHub least-privilege automation credential path.
@ -428,11 +428,11 @@ A healthy ByteLyst Hermes setup should be:
- [x] Evaluate private-only dashboard/mission-control UX. - [x] Evaluate private-only dashboard/mission-control UX.
- vijay: root dashboard is reachable via Tailscale at `http://100.87.53.10:9119/`. - vijay: root dashboard is reachable via Tailscale at `http://100.87.53.10:9119/`.
- bheem: Uma dashboard is reachable via Tailscale at `http://100.87.53.10:9120/`. - bheem: Uma dashboard is reachable via Tailscale at `http://100.87.53.10:9120/`.
- [ ] Add Kanban/multi-agent workflow documentation if it fits ByteLyst's solo-operator workflow. - [x] Add Kanban/multi-agent workflow documentation if it fits ByteLyst's solo-operator workflow.
- [x] Add silent-on-success system watchdogs. - [x] Add silent-on-success system watchdogs.
- vijay: root watchdog is deployed as silent-on-success and now covers gateway, cron, backup freshness, disk, memory, Caddy, and Gitea container health. - vijay: root watchdog is deployed as silent-on-success and now covers gateway, cron, backup freshness, disk, memory, Caddy, and Gitea container health.
- [ ] Clean up stale memory/skills and pin critical skills. - [x] Clean up stale memory/skills and pin critical skills.
- [ ] Schedule quarterly restore drills. - [x] Schedule quarterly restore drills.
- vijay: quarterly restore drill reminder cron is configured for root. - vijay: quarterly restore drill reminder cron is configured for root.
- bheem: Uma-specific quarterly restore drill is not configured yet; follow-up needed if Uma gets a persistent backup workflow. - bheem: Uma-specific quarterly restore drill is not configured yet; follow-up needed if Uma gets a persistent backup workflow.
@ -444,9 +444,9 @@ This roadmap is complete when:
- vijay: upgrade path was executed against shared checkout `0b6ace649`; restore rehearsal succeeded into `/tmp/hermes-restore-test-root`. Full rollback remains a manual operator decision but the documented restore process is tested. - vijay: upgrade path was executed against shared checkout `0b6ace649`; restore rehearsal succeeded into `/tmp/hermes-restore-test-root`. Full rollback remains a manual operator decision but the documented restore process is tested.
- [x] Gateway failures and backup failures notify Telegram. - [x] Gateway failures and backup failures notify Telegram.
- [x] At least one fallback model/provider is configured and tested. - [x] At least one fallback model/provider is configured and tested.
- [ ] Web/search tooling works for current research tasks. - [x] Web/search tooling works for current research tasks.
- [x] No Hermes dashboard/API is publicly exposed. - [x] No Hermes dashboard/API is publicly exposed.
- [ ] Backup restore has been tested into a non-production profile. - [x] Backup restore has been tested into a non-production profile.
- vijay: root backup restored into temporary non-production `HERMES_HOME=/tmp/hermes-restore-test-root`; portable artifacts verified and raw `state.db` absent. - vijay: root backup restored into temporary non-production `HERMES_HOME=/tmp/hermes-restore-test-root`; portable artifacts verified and raw `state.db` absent.
- bheem: Uma restore has not been tested; no Uma persistent backup restore path exists yet. - bheem: Uma restore has not been tested; no Uma persistent backup restore path exists yet.
- [x] Core ByteLyst Hermes procedures exist as docs or skills. - [x] Core ByteLyst Hermes procedures exist as docs or skills.

View File

@ -639,21 +639,21 @@ Before final response:
Update this checklist only after each item has evidence from source review, tests, build output, or browser verification. Update this checklist only after each item has evidence from source review, tests, build output, or browser verification.
- [ ] Existing dashboard architecture inspected and summarized in implementation notes. - [x] Existing dashboard architecture inspected and summarized in implementation notes.
- [ ] Data model and mock service implemented outside UI components. - [x] Data model and mock service implemented outside UI components.
- [ ] `/hermes` mission control route renders from the service layer. - [x] `/hermes` mission control route renders from the service layer.
- [ ] `/hermes/tasks` ledger has search, filters, sorting, pagination, expandable details, and JSON export. - [x] `/hermes/tasks` ledger has search, filters, sorting, pagination, expandable details, and JSON export.
- [ ] `/hermes/tasks/[id]` detail route shows summary, timeline, execution details, and learning sections. - [x] `/hermes/tasks/[id]` detail route shows summary, timeline, execution details, and learning sections.
- [ ] `/hermes/products` portfolio route includes priority, attention, no-recent-activity, repeated-failure, and recently-shipped views. - [x] `/hermes/products` portfolio route includes priority, attention, no-recent-activity, repeated-failure, and recently-shipped views.
- [ ] `/hermes/history` route includes historical analytics with charts or accessible visual bars. - [x] `/hermes/history` route includes historical analytics with charts or accessible visual bars.
- [ ] `/hermes/agents` route shows agent/tool/integration health. - [x] `/hermes/agents` route shows agent/tool/integration health.
- [ ] `/hermes/settings` route shows editable-looking configuration panels and import/export affordances backed by mock data. - [x] `/hermes/settings` route shows editable-looking configuration panels and import/export affordances backed by mock data.
- [ ] Documentation created or updated with routes, run commands, mock data locations, and real telemetry integration plan. - [x] Documentation created or updated with routes, run commands, mock data locations, and real telemetry integration plan.
- [ ] Lint passes or any pre-existing lint failures are explicitly identified. - [x] Lint passes or any pre-existing lint failures are explicitly identified.
- [ ] Typecheck passes. - [x] Typecheck passes.
- [ ] Unit/component tests pass. - [x] Unit/component tests pass.
- [ ] Production build passes. - [x] Production build passes.
- [ ] E2E or browser smoke verification covers all new routes with no console errors. - [x] E2E or browser smoke verification covers all new routes with no console errors.
- [ ] Responsive layout checked at desktop and mobile widths. - [ ] Responsive layout checked at desktop and mobile widths.
Known roadmap assumptions to handle safely during implementation: Known roadmap assumptions to handle safely during implementation:

View File

@ -53,6 +53,7 @@ Current key files:
- `docs/hermes-setup-upgrade-roadmap.md` - `docs/hermes-setup-upgrade-roadmap.md`
- `docs/hermes-operations.md` - `docs/hermes-operations.md`
- `docs/vm-security-blind-spots-roadmap.md` - `docs/vm-security-blind-spots-roadmap.md`
- `docs/vm-exposure-inventory.md`
### `.github/workflows/` ### `.github/workflows/`

View File

@ -0,0 +1,151 @@
# ByteLyst VM Exposure Inventory
**Generated:** 2026-05-27
**Host:** `srv1491630`
**Purpose:** Phase 0 inventory for `docs/vm-security-blind-spots-roadmap.md`.
This inventory is a pre-change control document. It does not approve exposure by itself. Each `Needs decision` row requires owner approval before firewall, Compose, Caddy, or SSH changes.
## Classification Key
| Class | Meaning | Expected Controls |
| --- | --- | --- |
| `public-caddy` | Public app/API intended to be reached through Caddy | Caddy TLS, hostname/path routing, app auth where needed, no direct host-port exposure |
| `public-direct` | Direct host-port access intentionally public | explicit approval, provider/UFW allowance, monitoring |
| `private-admin` | Admin/dev/internal tool | Tailscale/VPN, SSH tunnel, IP allowlist, or auth gate |
| `loopback-only` | Host-local service used by Caddy or local automation | bind `127.0.0.1:port`; no external bind |
| `docker-internal` | Container-to-container only | no host port mapping |
| `retire` | Unused/deprecated | remove service or disable host exposure |
| `needs-decision` | Existing exposure with unknown/unclear intent | owner must classify before remediation |
## Caddy Public Routes
| Hostname/path | Upstream | Initial class | Decision needed |
| --- | --- | --- | --- |
| `api.bytelyst.com/platform/*` | `platform-service:4003` | `public-caddy` | Confirm auth posture |
| `api.bytelyst.com/extraction/*` | `extraction-service:4005` | `public-caddy` | Confirm auth posture |
| `api.bytelyst.com/mcp/*` | `mcp-server:4007` | `public-caddy` | Confirm public need |
| `api.bytelyst.com/peakpulse/*` | `peakpulse-backend:4010` | `public-caddy` | Confirm direct host port can close |
| `api.bytelyst.com/chronomind/*` | `chronomind-backend:4011` | `public-caddy` | Confirm direct host port can close |
| `api.bytelyst.com/jarvisjr/*` | `jarvisjr-backend:4012` | `public-caddy` | Confirm direct host port can close |
| `api.bytelyst.com/nomgap/*` | `nomgap-backend:4013` | `public-caddy` | Confirm direct host port can close |
| `api.bytelyst.com/mindlyst/*` | `mindlyst-backend:4014` | `public-caddy` | Confirm direct host port can close |
| `api.bytelyst.com/lysnrai/*` | `lysnrai-backend:4015` | `public-caddy` | Confirm direct host port can close |
| `api.bytelyst.com/notelett/*` | `notelett-backend:4016` | `public-caddy` | Confirm direct host port can close |
| `api.bytelyst.com/flowmonk/*` | `flowmonk-backend:4017` | `public-caddy` | Confirm direct host port can close |
| `api.bytelyst.com/actiontrail/*` | `actiontrail-backend:4020` | `public-caddy` | Confirm direct host port can close |
| `api.bytelyst.com/localmemgpt/*` | `localmemgpt-backend:4019` | `public-caddy` | Confirm direct host port can close |
| `api.bytelyst.com/invttrdg/*` | `invttrdg-backend:4018` | `public-caddy` | Confirm direct host port can close |
| `api.bytelyst.com/devops/*` | `devops-backend:4004` | `private-admin` | Should require auth/private access |
| `gitea.bytelyst.com` | `gitea-npm-registry:3000` | `public-caddy` | Confirm direct `3300` can close |
| `admin.bytelyst.com` | `admin-web:3001` | `private-admin` | Confirm route still resolves; upstream container not in current `docker ps` |
| `devops.bytelyst.com` | `devops-web:3000` | `private-admin` | Should require auth/private access |
| `tracker.bytelyst.com` | `tracker-web:3003` | `public-caddy` | Confirm direct host port can close |
| `llmlab.bytelyst.com` | `llmlab-dashboard:3075` | `private-admin` | Dashboard currently unhealthy; decide public/private/retire |
| `ollama.bytelyst.com` | `172.17.0.1:11434` | `private-admin` | Model endpoint should not be unauthenticated public |
| `trading-api.bytelyst.com` | `trading-backend:5000` | `public-caddy` | Confirm auth posture |
| `invttrdg.bytelyst.com` | `invttrdg-web:3085` | `public-caddy` | Confirm direct host port can close |
| `notes.bytelyst.com` | `notelett-web:3045` | `public-caddy` | Confirm direct host port can close |
| `clock.bytelyst.com` | `chronomind-web:3030` | `public-caddy` | Confirm direct host port can close |
## Public Bind Inventory
These listeners were bound on `0.0.0.0` and/or `[::]` during review.
| Port | Service/container | Owner / Compose source | Current route | Initial class | Proposed action |
| --- | --- | --- | --- | --- | --- |
| `22` | `sshd` | host systemd | direct SSH | `public-direct` | Keep public only after SSH key hardening |
| `80`, `443` | `caddy` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | public ingress | `public-caddy` | Keep public |
| `3000` | `notelett-web` | `/opt/bytelyst/learning_ai_notes/docker-compose.yml` | `notes.bytelyst.com` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `3002` | `lysnrai-dashboard` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | none found in Caddy | `needs-decision` | Private/admin or retire direct exposure |
| `3003` | `tracker-web` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | `tracker.bytelyst.com` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `3030` | `chronomind-web` | `/root/bytelyst.ai/repos/learning_ai_clock/docker-compose.yml` | `clock.bytelyst.com` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `3035` | `jarvisjr-web` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | none found in Caddy | `needs-decision` | Unhealthy; classify as private/admin or retire |
| `3040` | `flowmonk-web` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | none found in Caddy | `needs-decision` | Unhealthy; classify as private/admin or retire |
| `3049` | `devops-web` | `/opt/bytelyst/bytelyst-devops-tools/dashboard/docker-compose.yml` | `devops.bytelyst.com` | `private-admin` with direct bypass | Fix old repo path drift, then bind loopback/private |
| `3050` | `mindlyst-web` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | none found in Caddy | `needs-decision` | Unhealthy; classify as private/admin or retire |
| `3055` | `nomgap-web` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | none found in Caddy | `needs-decision` | Unhealthy; classify as private/admin or retire |
| `3060` | `actiontrail-web` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | none found in Caddy | `needs-decision` | Unhealthy; classify as private/admin or retire |
| `3070` | `localmemgpt-web` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | none found in Caddy | `needs-decision` | Unhealthy; classify as private/admin or retire |
| `3075` | `llmlab-dashboard` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | `llmlab.bytelyst.com` | `private-admin` with direct bypass | Dashboard unhealthy; gate or retire |
| `3085` | `invttrdg-web` | `/opt/bytelyst/learning_ai_invt_trdg/docker-compose.yml` | `invttrdg.bytelyst.com` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `3100` | `loki` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | none found in Caddy | `private-admin` | Remove public host bind; keep Docker/internal or Tailscale only |
| `3300` | `gitea-npm-registry` | non-Compose container labels absent | `gitea.bytelyst.com` | `public-caddy` with direct bypass | Bind loopback or private; keep Caddy route |
| `4004` | `devops-backend` | `/opt/bytelyst/learning_ai_devops_tools/dashboard/docker-compose.yml` | `api.bytelyst.com/devops/*` | `private-admin` with direct bypass | Bind loopback/private |
| `4010` | `peakpulse-backend` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | `api.bytelyst.com/peakpulse/*` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `4011` | `chronomind-backend` | `/root/bytelyst.ai/repos/learning_ai_clock/docker-compose.yml` | `api.bytelyst.com/chronomind/*` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `4012` | `jarvisjr-backend` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | `api.bytelyst.com/jarvisjr/*` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `4013` | `nomgap-backend` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | `api.bytelyst.com/nomgap/*` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `4014` | `mindlyst-backend` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | `api.bytelyst.com/mindlyst/*` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `4015` | `lysnrai-backend` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | `api.bytelyst.com/lysnrai/*` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `4016` | `notelett-backend` | `/opt/bytelyst/learning_ai_notes/docker-compose.yml` | `api.bytelyst.com/notelett/*` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `4017` | `flowmonk-backend` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | `api.bytelyst.com/flowmonk/*` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `4019` | `localmemgpt-backend` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | `api.bytelyst.com/localmemgpt/*` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `4020` | `actiontrail-backend` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | `api.bytelyst.com/actiontrail/*` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `4025` | `invttrdg-backend` | `/opt/bytelyst/learning_ai_invt_trdg/docker-compose.yml` | `api.bytelyst.com/invttrdg/*` | `public-caddy` with direct bypass | Bind loopback or remove host port after Caddy smoke |
| `1025` | `mailpit` SMTP | `/root/bytelyst.ai/repos/learning_ai_common_plat/docker-compose.yml` | none found in Caddy | `private-admin` / `docker-internal` | Remove public host bind |
| `8025` | `mailpit` UI | `/root/bytelyst.ai/repos/learning_ai_common_plat/docker-compose.yml` | none found in Caddy | `private-admin` | Bind loopback/Tailscale or remove |
| `10000` | `azurite` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | none found in Caddy | `docker-internal` | Remove public host bind |
| `1234`, `8081` | `cosmos-emulator` | `/opt/bytelyst/learning_ai_common_plat/docker-compose.ecosystem.yml` | none found in Caddy | `docker-internal` | Remove public host bind |
| `11434` | `ollama` host process | host service | `ollama.bytelyst.com` | `private-admin` | Bind loopback/private or auth-gate; do not leave raw public |
## Non-Public / Internal Listeners
| Address/port | Process/service | Initial class | Notes |
| --- | --- | --- | --- |
| `127.0.0.53:53`, `127.0.0.54:53` | `systemd-resolve` | host-internal | Expected resolver listeners |
| `127.0.0.1:44561` | `ollama` | host-internal | Secondary loopback listener observed |
| `100.87.53.10:9119`, `100.87.53.10:9120` | `hermes` | private-admin | Tailscale-only bind; keep private |
| `100.87.53.10:51855`, `[fd7a:115c:a1e0::3c33:350a]:43379` | `tailscaled` | private-admin | Tailscale control/data listeners |
| Docker-internal only | `platform-service`, `mcp-server`, `extraction-service`, `prometheus`, `cadvisor`, `node-exporter`, `valkey`, `trading-backend` | docker-internal/private | No direct host bind seen, except Caddy may route to some by service name |
## Unhealthy Containers At Inventory Time
| Container | Port exposure | Initial action |
| --- | --- | --- |
| `learning_ai_common_plat-llmlab-dashboard-1` | `0.0.0.0:3075` and Caddy `llmlab.bytelyst.com` | Fix/gate/retire before treating public |
| `learning_ai_common_plat-actiontrail-web-1` | `0.0.0.0:3060` | Classify and fix/retire |
| `learning_ai_common_plat-jarvisjr-web-1` | `0.0.0.0:3035` | Classify and fix/retire |
| `learning_ai_common_plat-localmemgpt-web-1` | `0.0.0.0:3070` | Classify and fix/retire |
| `learning_ai_common_plat-nomgap-web-1` | `0.0.0.0:3055` | Classify and fix/retire |
| `learning_ai_common_plat-flowmonk-web-1` | `0.0.0.0:3040` | Classify and fix/retire |
| `learning_ai_common_plat-mindlyst-web-1` | `0.0.0.0:3050` | Classify and fix/retire |
## Drift / Follow-Up Findings
- `devops-backend` runs from `/opt/bytelyst/learning_ai_devops_tools/dashboard/docker-compose.yml`.
- `devops-web` runs from `/opt/bytelyst/bytelyst-devops-tools/dashboard/docker-compose.yml`, an older path. Align this before changing devops dashboard port bindings.
- `gitea-npm-registry` has no Compose labels in Docker inspect output. Find its systemd/compose owner before changing `3300`.
- `admin.bytelyst.com` points at `admin-web:3001`, but no `admin-web` container was present in `docker ps` during this inventory.
## Proposed First Remediation Groups
Do these in separate commits/windows with smoke checks after each group.
1. **Internal emulators and mail tools:** `1025`, `8025`, `10000`, `1234`, `8081`.
- Expected class: `docker-internal` or `private-admin`.
- Preferred fix: remove host port mappings or bind to `127.0.0.1`.
2. **Observability internals:** `3100` and any future Prometheus/Grafana/exporter direct binds.
- Expected class: `private-admin`.
- Preferred fix: Docker-internal or Tailscale-only.
3. **Admin/model surfaces:** `11434`, `3075`, `3049`, `4004`.
- Expected class: `private-admin`.
- Preferred fix: auth gate/private route and no raw public port.
4. **Caddy-backed app/API direct bypass ports:** `3000`, `3003`, `3030`, `3085`, `4010`-`4025`.
- Expected class: `public-caddy`.
- Preferred fix: keep Caddy public, remove raw direct public binds.
5. **SSH:** `22`.
- Expected class: `public-direct`.
- Preferred fix: keep public only after key-only and root-login hardening.
## Verification Commands
```bash
date -Is
ss -ltnp
docker ps --format '{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
docker ps -q | xargs -r docker inspect --format '{{.Name}}\tproject={{index .Config.Labels "com.docker.compose.project"}}\tservice={{index .Config.Labels "com.docker.compose.service"}}\tworkdir={{index .Config.Labels "com.docker.compose.project.working_dir"}}\tconfig={{index .Config.Labels "com.docker.compose.project.config_files"}}'
docker exec caddy caddy validate --config /etc/caddy/Caddyfile
sed -n '1,260p' /opt/bytelyst/Caddyfile
iptables -S DOCKER-USER
```

View File

@ -377,7 +377,7 @@ Effective `sshd -T` settings showed:
### Phase 0 — Freeze and inventory before changes ### Phase 0 — Freeze and inventory before changes
- [ ] Freeze new public hostnames/ports until the exposure inventory is complete. - [ ] Freeze new public hostnames/ports until the exposure inventory is complete.
- [ ] Generate `docs/vm-exposure-inventory.md` from Docker, Caddy, `ss`, and DNS. - [x] Generate `docs/vm-exposure-inventory.md` from Docker, Caddy, `ss`, and DNS.
- [ ] Mark each exposed service as `public`, `private`, `internal-only`, or `retire`. - [ ] Mark each exposed service as `public`, `private`, `internal-only`, or `retire`.
- [ ] Review with S before changing public access for customer/user-facing apps. - [ ] Review with S before changing public access for customer/user-facing apps.