Files
OwnCord/docs/audit-2026-04-07.md
T
J3vb 4ab01c39df docs(audit): close plugin CRITICALs 1-4, accept #5 as residual risk
P3 item 4. Each of the five plugin CRITICALs in audit-2026-04-07.md was
re-verified against the current Server/plugin/ code rather than the tracker:

- #1 invokeCommand timeout — CLOSED. Per-call CPU budget (manifest →
  config → 100ms floor) + WithCloseOnContextDone + lazy re-instantiation.
  Landed in PR #1182 (7b178ff, b13adf2); pinned by the W1-1 test.
- #2 storage key isolation — CLOSED. The premise did not hold: the namespace
  is the caller's Instance.ID and plugin_kv PRIMARY KEY (plugin_id, key).
- #3 per-command ACL — CLOSED by the manifest `commands` ACL in 3d2dd19.
- #4 event rate limit — CLOSED as not reachable: EventSink.Dispatch invokes
  no guest code and has zero callers; the requirement is recorded as a
  SECURITY GATE at the point delivery would be wired.
- #5 HTTP exfiltration — OPEN, accepted residual risk. An allowlisted host is
  by definition a permitted destination; closing it needs egress content
  policy and per-plugin allowlists, i.e. a runtime redesign, out of scope
  for P3.

Because #5 stays open the standing rule fires as written: plugins ship
default-disabled at the beta gate. Re-verified in config.DefaultConfig() —
Plugins.Enabled false, HTTPAllowlist empty. Also records the structural
mitigation covering #2/#4/#5: no host imports are wired into the wazero
runtime, so command_dispatch and list_commands are the only guest-reachable
entry points today.

Mirrors the outcome in the §1 carried-over row of audit-2026-07-19.md,
records decision D11 in plans/audit-2026-07-19-decisions.md, and notes in
plans/slash-commands.md which slice of its manifest design already landed.
2026-07-20 14:10:15 +02:00

28 KiB
Raw Blame History

OwnCord — Comprehensive Project Audit

Date: 2026-04-07
Branch: claude/plan-phases-b-c-bGpoS
Audited by: 6 parallel Claude agents across 8 dimensions


Finding closure status (maintained; last updated 2026-07-20)

Every CRITICAL/HIGH below must end with a closing commit link or an explicit mitigation before the beta gate. Standing rule: any plugin CRITICAL still OPEN at the beta gate → plugins ship default-disabled.

Rule status 2026-07-20: finding #5 is closed as accepted residual risk, not fixed, so the rule fires — plugins ship default-disabled at beta. Verified in code: config.DefaultConfig() sets Plugins.Enabled: false and Plugins.HTTPAllowlist: []string{} (Server/config/config.go:206-212), and hostAllowed denies every host against an empty allowlist (Server/plugin/host_http.go:140-158, pinned by TestEmptyAllowlistDeniesEveryHost).

Structural mitigation covering #2, #4 and #5: no host imports are wired into the wazero runtime. activateWithRuntime instantiates guest modules with WASI preview-1 only (Server/plugin/sandbox_wazero.go:69-124), and HTTPDo / Storage* / EventSink.Dispatch have no callers outside the plugin package's own tests. The only guest-reachable entry points today are command_dispatch (via the WS chat_command handler) and list_commands at activation. Wiring those host imports is what makes #5 exploitable at all and is the point at which #4's rate limit must exist.

# Sev Finding Status
1 CRITICAL Plugin invokeCommand has no timeout CLOSED 2026-07-20 — verified in code. Every guest call (allocate / command_dispatch / deallocate) runs under a per-invocation deadline: budgetMs = manifest resources.cpu_budget_msplugins.cpu_budget_ms → hard 100 ms floor, applied via context.WithTimeout (Server/plugin/sandbox_wazero.go:257-268). The runtime is built WithCloseOnContextDone(true) (sandbox_wazero.go:69-73) so an expired deadline interrupts a runaway guest (for {}), and releaseClosedModule (sandbox_wazero.go:326-338) drops the closed module so the next dispatch re-instantiates lazily instead of bricking the plugin (regression W1-1). Landed in PR #1182 (0f58ddd budget, 2111976 W1-1). Pinned by TestWazeroCPUBudgetOverrunDoesNotBrickPlugin (sandbox_wazero_test.go:271)
2 CRITICAL Plugin storage has no per-plugin key isolation CLOSED 2026-07-20 — the finding's premise does not hold against the code. Isolation is structural, not a check that can be skipped: every Storage* call passes the caller's Instance.ID as the namespace and exposes no parameter by which a caller — let alone a guest module — could name another plugin's namespace (Server/plugin/host_storage.go:26-73), and plugin_kv PRIMARY KEY (plugin_id, key) (Server/migrations/015_plugins.sql:13-18) makes the same split the storage layout. Every query filters on plugin_id (Server/db/plugin_queries.go:87-135). This PR adds TestStorageKeysIsolatedPerPlugin pinning it (same key from two plugins does not collide; scan/delete do not cross namespaces) plus the missing key-size cap the file's doc comment already promised
3 CRITICAL Plugin per-command ACL missing (auto-registration) CLOSED 2026-07-20 (this PR) — the manifest is now the per-command ACL. plugin.json gains a commands block; RegisterCommand refuses any name the manifest did not declare (Server/plugin/host_commands.go:31-45, ErrCommandNotDeclared), which is the single choke point both list_commands auto-registration (sandbox_wazero.go:146-153) and direct registration route through. A guest can therefore no longer widen its own command surface, and an admin can see the full command list before enabling. Declared names are validated to the dispatcher's canonical form, deduplicated, and capped at 64 (manifest.go:207-233). Cross-plugin hijack was already refused and stays refused. Pinned by TestRegisterCommandRequiresManifestDeclaration + TestManifestCommandsValidation
4 CRITICAL No rate limit on event delivery to plugins CLOSED 2026-07-20 (not reachable) — there is no event-delivery path to rate-limit. EventSink.Dispatch invokes no guest code in either build (the loop body is inert) and nothing in the server calls it: the WS hub never dispatches to the sink, and no on_event host wiring exists. A plugin cannot slow the hub by handling events slowly because it never handles one. Recorded as a gate rather than left silent: the SECURITY GATE comment on Server/plugin/host_events.go:90-105 requires the per-plugin rate limit, the invokeCommand CPU deadline, and off-hub-goroutine delivery to land in the same change that wires guest delivery. TestEventDeliveryHasNoGuestPath fails if delivery appears without that review
5 CRITICAL Plugin HTTP capability allows data exfiltration to allowlisted hosts OPEN — accepted residual risk (2026-07-20). Not fixable by hardening: an allowlisted host is by definition a permitted destination, so a plugin holding http can POST anything it can read to it. Closing it properly needs egress content policy (per-plugin request/response body inspection, byte budgets, per-plugin allowlists instead of one server-wide list) — a plugin-runtime redesign, not a patch. Standing mitigations, all verified in code: (a) plugins.enabled defaults false; (b) plugins.http_allowlist defaults empty and an empty allowlist denies every host, so the capability is inert until an operator names a destination; (c) the manifest must declare http, which is visible to the admin before enabling; (d) no host import is wired, so guest code cannot call HTTPDo at all today; (e) SSRF hardening (allowlist dot-boundary matching, guarded dial that vets every resolved IP before connecting, redirect re-checks, 5 MiB response cap) confines reach to public allowlisted hosts. Residual risk accepted for alpha/beta: an operator who both enables plugins and allowlists a host trusts the plugins they install with data those plugins can read
6 HIGH Server/store/ untested SUPERSEDED — store/ package is being removed in P4 (single data layer); tests move to in-memory SQLite
7 HIGH Client src/lib/src/stores <10% unit coverage CLOSED since audit — large vitest suite exists (113 files); suite health tracked in P2
8 HIGH Unpinned critical npm packages OPEN — review in P2
9 MEDIUM auth_handler bypasses service layer OPEN — P4 consolidation candidate
10 MEDIUM Audit-trail write failures silently ignored OPEN — cheap fix, fold into P1
11 MEDIUM E2E not in CI / no .nvmrc IN PROGRESS — nightly non-blocking e2e job planned in P2

Table of Contents

  1. Architecture
  2. Code Quality
  3. Security
  4. Dependencies & Supply Chain
  5. Test Coverage & Quality
  6. CI/CD & DevEx
  7. Observability
  8. Plugin System Governance
  9. Prioritized Top-10 Action List

1. Architecture

Layer Map

Layer Location Responsibility
Client UI Client/tauri-client/src/components/ SolidJS UI components, settings, voice
Client Pages Client/tauri-client/src/pages/ MainPage, ConnectPage entry points
Client Stores Client/tauri-client/src/stores/ Reactive state (auth, channels, messages, voice, dm, ui, roles)
Client Lib Client/tauri-client/src/lib/ API client, WebSocket client, dispatcher, LiveKit session, theme
Tauri Rust Client/tauri-client/src-tauri/ WS proxy, credential manager, PTT, update notifications
Server API Server/api/ REST handlers (auth, channels, messages, DMs, invites, uploads)
Server WS Server/ws/ Real-time hub, event persistence, command dispatch, LiveKit integration
Server Service Server/service/ Domain business logic (Message, Channel, Permission, User, DM, Invite, Block, Voice)
Server Store Server/store/ Data access layer (SQLiteStore, PostgresStore stub, MemStore)
Server DB Server/db/ SQL schema, migrations, sqlc-generated queries
Server Plugin Server/plugin/ WASM runtime (Wazero), loader, manifest, registry, capability-scoped host APIs
Server Admin Server/admin/ Admin UI, backup management, log streaming
Server Auth Server/auth/ Authentication, session management, rate limiting, TOTP

Communication Patterns

Channel Usage
REST API Auth, channel CRUD, file uploads, initial data fetch — uses Tauri HTTP plugin for self-signed cert support
WebSocket Real-time events (messages, presence, typing, voice) — single connection per client, envelope format {type, id, payload}
Tauri IPC ws_connect/send/disconnect, credential manager, PTT, update checks — desktop-only features
LiveKit HTTP voice token acquisition, reverse-proxy WebSocket signaling at /livekit/*, webhook for participant events

Dependency Direction

Layering is healthy and mostly respected:

  • api/ → service/ → store/ holds
  • ws/ does not import api/ (no circular deps)
  • Plugin system imports service layer cleanly
  • Auth middleware isolated; not interspersed with handlers

Anti-patterns

SEVERITY File Finding
MEDIUM Server/api/auth_handler.go Auth handler queries DB directly, bypassing service layer — inconsistent with channel_handler.go pattern
MEDIUM Client/tauri-client/src/lib/dispatcher.ts 84 registered listeners creates implicit coupling; hard to trace data flow
MEDIUM Client/tauri-client/src/lib/livekitSession.ts (1710 lines) Monolith mixing LiveKit SDK, audio pipeline, diagnostics — should decompose
MEDIUM Client/tauri-client/src/pages/MainPage.ts (554 lines) Mixes routing, layout, store subscriptions, cleanup — violates single responsibility
MEDIUM Server/service/message.go (715 lines) Combines send/search/delete/edit/fetch — split into focused services
LOW Client/tauri-client/src/main.ts (538 lines) Entry point handles init, error handlers, theme, health checks, connection orchestration
LOW Server/api/router.go (394 lines) Mixes middleware setup, route registration, and business logic wiring
LOW Server/admin/ Admin package operates directly on DB; not using service layer — may diverge from REST/WS semantics
LOW PostgreSQL backend Fully scaffolded but query methods stubbed; migration path undocumented

2. Code Quality

Go — Error Handling

SEVERITY File:Line Finding
MEDIUM Server/admin/logstream.go:398-426 SSE handler ignores write errors with _, _ = fmt.Fprintf() — client disconnections not detectable
MEDIUM Server/admin/handlers_backup.go:55,139 database.LogAudit() errors silently discarded with _ = — audit trail may silently fail
MEDIUM Server/admin/handlers_users.go (5 instances) LogAudit() errors discarded — affects compliance/security audit trail
LOW Server/updater/updater.go (13 instances) Excessive _ = error suppression; no logging fallback
LOW Server/db/db.go (9 instances) Unchecked errors in setup/teardown paths

Go — Interface Design

  • Strong: Store package has clear interface hierarchy (Store → MessageStore, ChannelStore, UserStore, SessionStore, RoleStore) with proper concrete implementations
  • ⚠️ Concern: Service layer handlers receive concrete *db.DB or *Hub rather than small interfaces — reduces testability

Go — Large Files (>800 lines)

File Lines
Server/ws/hub.go 919 — mixes client management, event persistence, plugin integration, voice key-holding
Server/store/memstore.go 766
Server/service/message.go 715
Server/store/postgres.go 833 — largely stubbed scaffolding

Go — Security-Relevant TODOs

File TODO
Server/ws/command.go Validate attachment URL scheme (require HTTPS) — security gap
Server/ws/registry.go Stack trace may contain sensitive function arguments
Server/ws/voice_leave.go Propagate context through livekit.RemoveParticipant() call
Server/ws/voice_e2ee.go Re-check key-holder status inside sendToUserIfInVoiceChannel
Server/store/sqlite.go Incomplete transaction-scoped store wrapper

TypeScript — Type Safety

SEVERITY File:Line Finding
MEDIUM Multiple components ~43 event handlers with untyped (e) parameters — should be typed as MouseEvent, DragEvent, etc.
LOW src/lib/audioPipeline.ts:75 Single as any cast (justified by eslint-disable comment; acceptable)

TypeScript — Large Components

File Lines Action
src/components/settings/AccountTab.ts 845 Extract: password-section.ts, totp-section.ts, status-selector.ts
src/components/EmojiPicker.ts 665 Extract emoji data file
src/components/MessageList.ts 662 Extract height-calc module
src/components/MessageInput.ts 599 Reasonable given feature density

TypeScript — Error Handling Gaps

SEVERITY File Finding
MEDIUM src/components/InviteManager.ts:96,151 .then() chains without .catch() — silent rejection
MEDIUM src/components/message-input/file-upload.ts:57 .then() without catch — corrupt file failures silent
MEDIUM src/components/message-list/attachments.ts:341 void fetchImageAsDataUrl().then() — image load failures not surfaced

3. Security

Overall Posture: GOOD (no critical issues in core app security)

Secrets & Configuration

  • .env.example uses placeholder values; file gitignored
  • No hardcoded secrets found in source, Tauri config, or configs
  • LiveKit credentials require 32+ character minimum

Input Validation

  • Username validation with auth.ValidateUsername() + bluemonday HTML sanitization
  • File uploads use filepath.Base() to prevent path traversal
  • Plugin uploads: magic-byte ZIP validation, 16 MiB hard cap, decompression bomb protection, symlink rejection
  • MEDIUM | Server/api/auth_handler.go:151 | Username validation doesn't explicitly check for Unicode control characters, RTL overrides, or zero-width chars — potential homograph attack vector

SQL Injection

  • All standard queries use parameterized statements
  • SearchMessagesInChannels uses fmt.Sprintf only for ? placeholder structure (not user data) — safe by design
  • FTS query sanitization: whitelist-only (letters, digits, spaces, hyphens), max 200 runes

Authentication & Authorization

  • AuthMiddleware: Bearer token extraction → hash → session lookup → expiry → ban check → context injection
  • RequirePermission: Admin bit (0x40000000) bypasses; 403 on insufficient permissions
  • WebSocket in-band auth within 10-second deadline; re-validated every 10 messages
  • Fail-closed: nil role = zero access
  • Permission-filtered reconnect replay (prevents data leakage after permission changes)

Rate Limiting

  • Per-endpoint configurable rate limits with 429 + Retry-After
  • Login, register, TOTP verification all rate-limited
  • Sensitive endpoints (account deletion, TOTP management) rate-limited
  • IP extraction validates trusted CIDR proxies — prevents rate-limit bypass via spoofed headers

Tauri Security

  • CSP enforced; withGlobalTauri: false (Tauri API not exposed on window)
  • Capabilities-based permission model (Tauri v2)
  • No devtools in production build

WebSocket Security

  • Auth deadline prevents resource exhaustion from slow clients
  • Priority-based send channels (high/normal/low) prevent low-priority spam blocking critical messages
  • Read limit enforced (wsReadLimitBytes = config.MaxMessageBytes)

Observations (not blocking)

SEVERITY Area Finding
MEDIUM Username validation Add explicit Unicode control character / zero-width char rejection
MEDIUM TOTP secrets Confirm AES-256 encryption of stored secrets with per-record salt
MEDIUM LiveKit webhook Confirm webhook signature validation cannot be bypassed with malformed JWT
LOW Session tokens Confirm cryptographically secure RNG and per-session salt for token hashing

4. Dependencies & Supply Chain

Overall Posture: MODERATE RISK (Go excellent, npm floating)

Go Modules — 30 direct deps, ALL exact-pinned

Notable: golang.org/x/crypto v0.49.0, github.com/corazawaf/coraza/v3 v3.6.0, github.com/tetratelabs/wazero v1.11.0, modernc.org/sqlite v1.48.0

npm — 13 production deps, ALL floating (^) ⚠️

Risk Package Version
HIGH @tauri-apps/plugin-updater ^2.10.0 — update mechanism, should pin
HIGH @tauri-apps/plugin-global-shortcut ^2 — any 2.x allowed
HIGH @tauri-apps/plugin-notification ^2 — any 2.x allowed
HIGH @tauri-apps/plugin-store ^2 — any 2.x allowed
MEDIUM livekit-client ^2.18.0
MEDIUM solid-js ^1.9.3
LOW zod ^4.3.6

Mitigating factor: package-lock.json (8,832 lines) is committed and locks transitive deps.

Lockfile Status

Ecosystem Status
Go go.sum committed, 455 entries
npm (client) package-lock.json committed, 8,832 lines
npm (root) package-lock.json committed, 492 lines

License Compliance

  • OwnCord: AGPLv3
  • All direct dependencies: Apache 2.0 or MIT — no copyleft incompatibilities

Known Vulnerabilities

  • No CRITICAL CVEs in direct dependencies as of Feb 2025
  • golang.org/x/crypto v0.49.0 — verify no disclosed CVEs since mid-2024 release
  • Run npm audit and govulncheck periodically

5. Test Coverage & Quality

Go — Package Coverage

Package Test Files Status
api/ 23 Heavy behavioral coverage
ws/ 32 Connect/disconnect, commands, auth, deadlock
auth/ 8 Core flows, TOTP, helpers
db/ 18 Operations and migrations
admin/ 11 Good coverage
plugin/ 6 Loading, sandbox, manifest
permissions/ 3
service/ 2 ⚠️ Only message + permission
**store/** 0 CRITICAL GAP — data persistence untested
syncutil/ 0 ⚠️ Concurrency utils untested
scripts/ 0 Low priority (CLI tool)

Go — Test Quality: GOOD

  • Behavioral tests using in-memory SQLite (:memory:) with full schema
  • Table-driven patterns used throughout
  • goleak.VerifyTestMain() in auth/ — goroutine leak detection
  • Deadlock detection tests via go test -tags deadlock

Go — Critical Coverage Gaps

SEVERITY Gap
HIGH Server/store/ — data persistence layer has zero test coverage
HIGH No PostgreSQL integration tests — all use in-memory SQLite; schema drift not caught
MEDIUM Server/syncutil/ — concurrency utilities untested
MEDIUM Migration safety not validated across versions

TypeScript — Test Files

  • 1 component unit test: src/components/solid/Badge.test.tsx
  • 44 E2E specs: Playwright (20 browser-mode, 22 native-mode + helpers)
  • 1 jsdom smoke test: tests/browser/smoke.test.ts

TypeScript — E2E Coverage: EXCELLENT

Auth flow, channels, messages, DMs, health/reconnect, UI overlays, voice controls, theme persistence — all covered in 44 Playwright specs.

TypeScript — Unit Coverage: MINIMAL (<10%)

SEVERITY Gap
HIGH No tests for src/lib/ utilities (api.ts, ws.ts, dispatcher.ts, livekitSession.ts)
HIGH No tests for src/stores/ (messages, channels, voice, auth)
MEDIUM 7 files explicitly excluded from coverage thresholds: main.ts, updater.ts, credentials.ts, etc.
MEDIUM 70% coverage threshold configured in vitest but unit tests too sparse to enforce

6. CI/CD & DevEx

Pipeline Gates

Job OS Gates
server-build-test Windows + Ubuntu Build (4 tag variants), Tests (race + deadlock), golangci-lint, govulncheck
client-check Windows TypeScript check, ESLint, Oxlint, Prettier, Vitest, npm audit, Knip
server-docker-build Ubuntu Docker build verification
tauri-build Windows + Ubuntu + Ubuntu-ARM Rust lint, Rust audit, full Tauri build — PR to main only

Linting Enforcement

  • Go: golangci-lint v2.11.3 — hard fail; rules: gocritic, gosec, errcheck, bodyclose, contextcheck, staticcheck
  • TypeScript: ESLint with no-floating-promises: error, no-unused-vars: error; Prettier format check; Oxlint
  • Enforcement: Hard fail for all linting/formatting in CI

Gaps

SEVERITY Finding
MEDIUM E2E tests not in main CI — only run on PRs to main, not on merge. Code could merge without E2E gate
MEDIUM No branch protection rules visible in repo config
LOW No .nvmrc / .node-version file — Node 20 only enforced in CI, not locally
LOW Rust toolchain pinned to stable (not specific version) — builds can drift
LOW Knip runs with `

Build Reproducibility

  • Go: go 1.25.0 pinned in go.mod; go.sum committed
  • Node: Version 20 pinned in CI; npm ci used (clean install)
  • 4 Go build tag variants tested: default, otel, wazero, otel+wazero
  • Lockfiles current (go.sum: Apr 6, package-lock: Apr 7)

7. Observability

Logging: STRONG

  • Library: log/slog (stdlib, Go 1.21) with multi-handler — stdout (INFO+) + ring buffer (DEBUG+)
  • Structured: All logs use key-value pairs (slog.Info("msg", "key", val))
  • Context present: actor_id, user_id, channel_id, operation names logged consistently
  • Admin log viewer: 2000-entry ring buffer in Server/admin/logstream.go
  • Minor: Server/scripts/seed.go uses fmt.Printf (non-production CLI, acceptable)

Metrics & Tracing: PRESENT (build-tag gated)

  • OpenTelemetry SDK with no-op default; full telemetry via -tags otel
  • Prometheus metrics export at /metrics
  • Request ID propagation via X-Request-Id header
  • Health endpoints: GET /health, GET /api/v1/health, GET /api/v1/livekit/health

Error Surfacing: GOOD

  • Errors logged server-side with slog.Error before HTTP response
  • Consistent HTTP status codes (400 validation, 503 feature unavailable, 500 internal)
  • writeErr() / writeJSON() helper in admin ensure consistent error response shape

Client-Side: LIMITED ⚠️

SEVERITY Finding
MEDIUM No global error boundary component — unhandled promise rejections may fail silently
LOW No crash/error reporting integration (Sentry, etc.)

8. Plugin System Governance

Plugin Architecture

  • WASM isolation via Wazero (behind -tags wazero build flag)
  • Manifest-declared capabilities: commands, events, storage, http, ui
  • Memory capped per runtime (default 64 MiB); CPU budget configurable

CRITICAL Issues

Closure status (2026-07-20): the table below is the original 2026-04-07 record and is kept verbatim. Current state lives in the closure table at the top of this document — findings 14 are closed; #5 (HTTP exfiltration to an allowlisted host) is accepted residual risk, which keeps plugins default-disabled at the beta gate. Line numbers below refer to the audited tree, not today's.

SEVERITY File:Line Finding
CRITICAL Server/plugin/sandbox_wazero.go:162,211 invokeCommand has no timeout — a looping plugin hangs the goroutine indefinitely
CRITICAL Server/plugin/host_storage.go Storage capability has no key isolation — plugin can read/write ANY key in the plugin store, not just its own
CRITICAL Server/plugin/host_http.go:162-240 HTTP capability allows plugins to exfiltrate data by POSTing captured payload to any allowlisted host
CRITICAL Server/plugin/registry.go:129-133 All commands auto-registered if manifest declares commands capability — no per-command ACL
CRITICAL Server/plugin/host_events.go No rate limit on event delivery to plugins — malicious plugin could slow server by processing events slowly
MEDIUM Server/plugin/host_http.go:64-96 DNS rebinding TOCTOU between rejectPrivateAddrs check and TCP dial — not fully atomic
LOW Server/plugin/registry.go Default (non-Wazero) stub build doesn't emit WARN if plugins are configured but stub is running
LOW Server/plugin/loader.go Plugin discovery walks filesystem on every server start — no manifest caching

Strengths

  • Per-plugin WASM module isolation (memory, execution)
  • Private IP SSRF protection via rejectPrivateAddrs()
  • Symlink rejection in ZIP extraction (zip-slip protection)
  • Decompression bomb protection on plugin upload
  • Graceful lifecycle (disable/uninstall/close) with module cleanup
  • Plugin registry tracks all loaded instances with mutex-protected map

9. Prioritized Top-10 Action List

Priority SEVERITY Area Action File
1 CRITICAL Plugin Add context.WithTimeout(ctx, cfg.CPUBudgetMs) around invokeCommand to prevent infinite hangs Server/plugin/sandbox_wazero.go:162
2 CRITICAL Plugin Namespace storage keys by plugin ID: fmt.Sprintf("%d/%s", pluginID, key) in PluginGet/Set/Delete Server/plugin/host_storage.go
3 CRITICAL Plugin Add per-command ACL in registry — check plugin.Manifest.AllowedCommands or require explicit command declaration Server/plugin/registry.go:129
4 CRITICAL Plugin Rate-limit event delivery to plugins (token bucket per plugin) to prevent event flooding DoS Server/plugin/host_events.go
5 HIGH Tests Add store/ package tests — data persistence is completely untested; cover SQLiteStore CRUD and query methods Server/store/
6 HIGH Tests Add TypeScript unit tests for src/lib/ (api.ts, ws.ts, dispatcher.ts) and src/stores/ — currently <10% unit coverage Client/tauri-client/src/lib/
7 HIGH Deps Pin critical npm packages to exact versions: @tauri-apps/plugin-updater, vite, @tauri-apps/plugin-store Client/tauri-client/package.json
8 MEDIUM Architecture Refactor Server/api/auth_handler.go to route through AuthService — currently bypasses service layer Server/api/auth_handler.go
9 MEDIUM Code Quality Silence audit trail loss — wrap database.LogAudit() calls with slog.Error on failure instead of _ = Server/admin/handlers_*.go
10 MEDIUM CI/CD Add E2E gate to ci.yml on push to main (not only on PR) + add .nvmrc for local Node version enforcement .github/workflows/ci.yml

Bonus (quick wins)

  • Add .catch() handlers to .then() chains in InviteManager.ts, file-upload.ts, attachments.ts
  • Add context.WithTimeout guard in Server/ws/voice_leave.go (livekit.RemoveParticipant has //nolint:contextcheck)
  • Add HTTPS-only URL validation in Server/ws/command.go (existing TODO)
  • Add frontend error boundary component wrapping MainPage
  • Add Unicode/invisible-character validation to username registration

Report generated from 6 parallel audit agents. All findings are read-only analysis; no code was modified.