Skip to content

UX Persona Audit — 2026-03-25 18:08 #1342

@Dexploarer

Description

@Dexploarer

Audit timestamp: 2026-03-25 18:08
Git state: develop @ a753a3409 — pulled from origin/develop before this run
Latest commits:

  • a753a34 Merge origin/develop into develop
  • 1535114 fix: unblock tests after header shell updates
  • 3fcdcdb feat: native header uses same glassmorphic pill container as companion
    Audit method: Fresh dev environment startup + API live-testing (localhost:31337) + static code analysis (Chrome not running; UI audit via source)
    App state: API :31337 ✅ Running — Dashboard UI :2138 ✅ Running
    Environment startup: PID 51342 — API ready in ~15s — UI ready in ~15s
    Note: Local stash applied before pull (2 modified files); stash not reapplied post-pull (intentional — auditing HEAD).

Runtime Environment

Service Port Status Startup
API + WebSocket 31337 ✅ Running ~15s
Dashboard UI (Vite) 2138 ✅ Running ~15s
Gateway 18789 Not tested

Environment Startup Log

Clean startup. Key log lines:

  • [milady] API server ready (1.0s)
  • [milady] http://localhost:2138/
  • [milady] Runtime ready — agent: Satoshi (total: 658ms)
  • Warn: @elizaos/plugin-edge-tts: no TEXT_TO_SPEECH handler on default export (non-fatal)
  • Cannot optimize dependency: stream-browserify (Vite warning, non-fatal)
  • 86 plugins reported "failed" in /api/health — all disabled plugins counted as failed (see ISS-05)

Issue Index

ID Severity Title
ISS-01 P1 cloud.apiKey returned in plaintext in GET /api/config response
ISS-02 P1 Dockerfile binds API to 0.0.0.0 with no default auth token
ISS-03 P2 GET /api/secrets unauthenticated — exposes masked private key presence
ISS-04 P2 GET /api/database/tables unauthenticated — full DB schema exposed
ISS-05 P2 /api/health reports 86 "failed" plugins — misleading ops metric
ISS-06 P3 <nav> in Header missing aria-label attribute
ISS-07 P3 Active nav buttons missing aria-current="page" for screen readers
ISS-08 P3 GET /api/permissions unauthenticated — leaks platform + shell-enabled flag


Persona Audits

1. Alex (Solo Dev) ⭐⭐⭐⭐ 4/5

Likes:

  • Dev environment boots cleanly in ~15s with a single bun run dev command — impressively fast for a full runtime + DB init + plugin loading stack.
  • CLAUDE.md is comprehensive; NODE_PATH, Bun exports patch, and port table are all documented. Avoids the most common setup pitfalls.
  • Plugin system loads 78 bundled skills at startup; AgentSkills catalog is functional immediately.
  • CLI entry and dev orchestrator are cleanly separated (entry.tsdev-ui.mjs).

Dislikes:

  • GET /api/config returns the live cloud.apiKey in plaintext (ISS-01). A dev who forwards port 31337 for remote testing inadvertently exposes their cloud credentials.
  • 86 plugins listed as "failed" in /api/health is alarming for first-time operators — these are disabled plugins, not actual load failures (ISS-05).
  • ELIZA_DEV_ONCHAIN=0 must be set manually to avoid Foundry/Anvil errors if not installed; not called out in CLAUDE.md quick-start.

Issues found:

  • ISS-01 (P1): GET /api/configcloud.apiKey returned in plaintext. Code at packages/agent/src/api/server.ts:12348 calls redactConfigSecrets(state.config). The redactDeep function at line 4217 applies SENSITIVE_KEY_RE (/api.?key/i) recursively; the key apiKey should match but empirically the full secret was returned in this run. Warrants investigation of runtime config object shape vs. regex.
  • ISS-05 (P3): packages/agent/src/api/server.ts:8406 — health endpoint bundles disabled plugins into failed count, creating false alarm signal.

2. Maria (Non-Technical Creator) ⭐⭐⭐ 3/5

Likes:

  • GET /api/onboarding/status returns {"complete":true} in ~5ms — onboarding state is persisted reliably.
  • CloudOnboarding component (packages/app-core/src/components/CloudOnboarding.tsx) is clean: single button, immediate auto-completion on connect, clear loading/error states.
  • Agent name ("Satoshi"), bio, and personality are fully customisable in milady.json; no code changes required.
  • UI language dropdown present in header with 6+ locale options (es, ko, pt, tl, vi, zh-CN — updated in this commit).

Dislikes:

  • "Connect to Eliza Cloud" as the sole onboarding CTA is opaque for a self-hosted user who doesn't know what "Eliza Cloud" is. No local-only path is surfaced at first glance.
  • OnboardingPanel step transitions are animated (onboarding-panel-enter) but the animation uses void panel.offsetHeight force-reflow hacks (OnboardingPanel.tsx:17) — can cause jank on low-end devices.
  • Error message in CloudOnboarding only parses "Open this link to log in: …" format; any other login errors display raw text, which may be confusing.

Issues found: None new beyond onboarding UX concerns (no code-level bugs confirmed).


3. Jordan (DevOps Engineer) ⭐⭐⭐ 3/5

Likes:

  • Dockerfile uses multi-stage build with slim runtime layer.
  • MILADY_STATE_DIR=/data/.milady ensures state survives container restarts when /data is a volume.
  • Railway port env var (PORT) is handled automatically: CMD … MILADY_PORT=${PORT:-2138}.
  • bun install is idempotent ("Checked 1758 installs across 2003 packages (no changes)").

Dislikes:

  • ISS-02 (P1): Dockerfile:173 sets ENV MILADY_API_BIND="0.0.0.0" but no default MILADY_API_TOKEN or ELIZA_API_TOKEN is set. isAuthorized() at packages/agent/src/api/server.ts:6169 returns true when no token is configured — entire API is open on all interfaces in container deployments without operator action.
  • No EXPOSE directive in Dockerfile — container port mapping is implicit, making it harder to self-document expected ports.
  • ELIZA_DEV_ONCHAIN=0 is required to skip Foundry; not set in Dockerfile ENV block.

Issues found:

  • ISS-02 (P1): Dockerfile:173 + packages/agent/src/api/server.ts:6169-6172. In Docker, API binds to 0.0.0.0 unauthenticated by default.

4. Sam (Crypto-Native Power User) ⭐⭐⭐⭐ 4/5

Likes:

  • GET /api/wallet/keys correctly returns HTTP 403 ("Wallet keys are only available during onboarding") post-onboarding — private keys are not exposed through this endpoint.
  • Wallet export has a hardened rejection path via resolveWalletExportRejection in packages/app-core/src/api/server-wallet-trade.ts:126 (rate limiting + confirmation delay documented).
  • BSC trade endpoints exist (buildBscBuyUnsignedTx, buildBscSellUnsignedTx, buildBscTradeQuote) — DeFi interaction is first-class.
  • SOLANA_PRIVATE_KEY and EVM_PRIVATE_KEY are listed in SENSITIVE_ENV_RESPONSE_KEYS in server-config-filter.ts:12,15 — filtered from env block of config responses.

Dislikes:

  • GET /api/secrets (unauthenticated) reveals SOLANA_PRIVATE_KEY: {isSet: true, maskedValue: "474P...Y5P1"} and EVM_PRIVATE_KEY: {isSet: true, maskedValue: "0x47...4dc8"} (ISS-03). While masked, confirming a funded wallet key is present is sensitive metadata that aids targeted attacks.
  • GET /api/config returns cloud.apiKey in full plaintext (ISS-01) — if an attacker reaches the API port, they can grab the cloud key.

Issues found:

  • ISS-03 (P2): packages/agent/src/api/server.ts:9699GET /api/secrets unauthenticated, returns masked key presence for all wallet and API secrets.

5. Priya (Enterprise Evaluator) ⭐⭐ 2/5

Likes:

  • Auth gate exists and is enforced for most routes when a token is configured (packages/agent/src/api/server.ts:8020-8025).
  • Prototype pollution is blocked via isBlockedObjectKey checking __proto__, constructor, prototype, and $include (packages/agent/src/api/server.ts:4154-4161).
  • Audit log system exists (packages/agent/src/security/audit-log.tsAUDIT_EVENT_TYPES, queryAuditFeed, subscribeAuditFeed).
  • Wallet export has rate-limiting and confirmation delay (hardened override in server-wallet-trade.ts).

Dislikes:

  • Zero-config is zero-auth: no token is set by default — any process that can reach port 31337 has full API access. This is the primary enterprise blocker.
  • ISS-01 (P1): Cloud API key returned plaintext in GET /api/config — one unauthenticated GET from any LAN host exposes the credential.
  • ISS-02 (P1): Docker deployment is open on 0.0.0.0 with no default token.
  • ISS-04 (P2): GET /api/database/tables (HTTP 200, unauthenticated) returns full schema with all table and column names — exposes internal data model to unauthenticated callers.
  • No TLS / mutual-TLS support visible in Dockerfile or server config.

Issues found:

  • ISS-04 (P2): packages/agent/src/api/server.ts — database/tables route behind the same auth gate that is open by default. All DB table schemas, column names, and types returned unauthenticated.

6. Chen (Plugin Developer) ⭐⭐⭐⭐ 4/5

Likes:

  • NODE_PATH is set in three consistent places (documented in CLAUDE.md) — plugin dynamic imports reliably resolve.
  • bun run setup:eliza-workspace symlinks local ../eliza — live package development without publish cycles.
  • scripts/patch-deps.mjs auto-patches broken Bun exports entries — addresses a real upstream issue without requiring manual intervention.
  • Plugin pre-registration logs are clear: "✓ @elizaos/plugin-form pre-registered (0ms)" — easy to trace load order.
  • 78 bundled skills available at startup (AgentSkills: Initialized with 78 skills).

Dislikes:

  • 86 plugins counted as "failed" in /api/health will confuse plugin devs who check health after their plugin installs — impossible to tell signal from noise (ISS-05).
  • stream-browserify Vite dependency warning at startup (Failed to resolve dependency: stream-browserify, present in 'optimizeDeps.include') — minor but creates noisy logs during plugin development.
  • ELIZA_DEV_ONCHAIN=0 must be set manually; plugin devs who forget will hit Foundry errors.

Issues found:

  • ISS-05 (P2): packages/agent/src/api/server.ts:8406 — health endpoint aggregates all disabled plugins as "failed". No way to distinguish load errors from disabled state.

7. Riley (Mobile-First User) ⭐⭐⭐⭐ 4/5

Likes:

  • Mobile hamburger menu is present (Menu icon, sm:hidden breakpoint) with slide-in drawer from right edge (slide-in-from-right duration-200).
  • Mobile nav buttons have min-h-[48px] touch targets — meets WCAG 2.5.5 (44×44px minimum).
  • OnboardingPanel uses max-md: breakpoints throughout and has max-md:max-h-[min(60dvh,calc(100dvh-8.5rem))] for safe area handling.
  • Header layout uses px-5 sm:px-8 responsive padding — adapts cleanly between breakpoints.
  • Mobile overlay uses w-[min(22rem,88vw)] — won't overflow narrow viewports.

Dislikes:

  • Desktop nav (<nav>) is hidden sm:flex — mobile users get no persistent navigation, only the hamburger drawer. Drawer requires an extra tap to reach any tab.
  • No viewport meta tag verification possible (Chrome not running), but the HTML entry point should be checked.
  • scrollbar-hide overflow-x-auto on desktop nav could cause hidden overflow on narrow landscape tablets.

Issues found: No P0–P2 issues. Minor UX friction with drawer-only mobile nav.


8. Taylor (Accessibility User) ⭐⭐⭐ 3/5

Likes:

  • Mobile menu trigger button has aria-label={t("aria.openNavMenu")} and aria-expanded={mobileMenuOpen} — correctly communicates state to screen readers (Header.tsx:334-336).
  • Mobile menu overlay has role="dialog", aria-modal="true", aria-label — proper dialog semantics (Header.tsx:385-387).
  • Close button has aria-label={t("aria.closeNavMenu")} (Header.tsx:394).
  • aria.* i18n keys exist for 20+ interactive elements (verified in es.json).
  • data-testid attributes on nav buttons enable reliable automated testing.

Dislikes:

  • ISS-06 (P3): The desktop <nav> element at Header.tsx:326 has no aria-label attribute. Screen readers announce it as an unlabeled landmark, which is ambiguous when multiple nav regions exist on the page.
  • ISS-07 (P3): Active nav buttons use CSS classes (HEADER_NAV_BUTTON_ACTIVE_CLASSNAME) and variant="default" to signal active state, but no aria-current="page" attribute is set (Header.tsx:338-348). Screen readers cannot identify the current page programmatically.
  • Mobile nav items in the drawer have no aria-current either (Header.tsx:444-462).

Issues found:

  • ISS-06 (P3): packages/app-core/src/components/Header.tsx:326<nav> missing aria-label.
  • ISS-07 (P3): packages/app-core/src/components/Header.tsx:338,448 — active nav buttons missing aria-current="page".

9. Jamie (Multi-Platform User) ⭐⭐⭐ 3/5

Likes:

  • Connector health system exists (packages/app-core/src/api/connector-health.ts).
  • GET /api/connectors returns {"connectors":{}} — confirms connector registry is live and queryable.
  • SwarmCoordinator is wired and started: "Chat callback wired", "WS broadcast callback wired" — multi-agent message routing is active.
  • BlueBubbles plugin enabled in config ("bluebubbles":{"enabled":true}).

Dislikes:

  • "connectors":{} — no connectors are active in this run (Telegram, Discord all disabled — no tokens set). Jamie would see an empty connectors page.
  • No visible Telegram/Discord quick-connect flow surfaced in the UI components audit; requires manual config file editing.
  • Signal pairing requires a separate pairing session flow that is complex to set up without docs.

Issues found: No code-level bugs. UX friction in connector setup flow.


10. Casey (First-Time Visitor) ⭐⭐⭐ 3/5

Likes:

  • GET /api/onboarding/status returns {"complete":true} — returning users skip onboarding immediately.
  • CloudOnboarding component has a clear single-action CTA and shows loading/error states.
  • Agent is named and personalised out of the box ("Satoshi") — not a blank slate.
  • Startup is fast enough (~15s cold start) that a first-time user won't wait long.

Dislikes:

  • First-time visitor arriving at localhost:2138 directly has no context — no product description, no "what is this?" explanation visible in CloudOnboarding.tsx (just "Connect to Eliza Cloud to get started").
  • The CloudOnboarding error branch only handles a specific URL pattern: elizaCloudLoginError?.match(/^Open this link to log in: (.+)$/). Any other OAuth/network error shows a raw error string to a non-technical user.
  • No onboarding exit or "skip cloud, run local" affordance visible in CloudOnboarding.tsx. User is gated on cloud connectivity.
  • Hard-coded "eliza" branding in the CloudOnboarding logo div may confuse Milady-branded deployments.

Issues found: UX gaps only. No P0–P2 code bugs.



Confirmed Non-Issues

  • GET /api/wallet/keys requires onboarding context — returns HTTP 403 post-onboarding. Not a route gap; intentional lifecycle gating.
  • GET /api/wallet returns 404 — route does not exist. Wallet state is accessed via sub-routes (/keys, export flow). Not a regression.
  • isAuthorized open-by-default — documented behaviour. When MILADY_API_TOKEN / ELIZA_API_TOKEN is set, all routes (except /api/auth/* and /api/onboarding/status) are properly gated at packages/agent/src/api/server.ts:8020-8025. The issue is the default-open state, not missing gate logic.
  • 86 "failed" plugins in health — these are disabled/unconfigured plugins, not runtime errors. Agent is fully functional. The metric label is misleading (ISS-05) but the runtime is healthy.
  • SENSITIVE_ENV_RESPONSE_KEYS missing cloud.apiKeyserver-config-filter.ts:filterConfigEnvForResponse only filters the env block. The cloud.apiKey field is in the cloud block, and redactDeep in server.ts:4217 uses SENSITIVE_KEY_RE (/api.?key/i) which should match apiKey. The plaintext return observed in this run warrants a dedicated investigation (ISS-01) — it may be a runtime config structure issue rather than a missing filter entry.
  • stream-browserify Vite warning — known upstream Vite incompatibility with Node built-ins. Non-fatal; does not affect runtime.

Prioritized Fix Checklist

P1 — Fix before next release

  • ISS-01: Investigate why cloud.apiKey is returned in plaintext from GET /api/config despite redactDeep applying SENSITIVE_KEY_RE. Add an explicit guard in redactConfigSecrets or in filterConfigEnvForResponse to strip cloud.apiKey from API responses.

    • Files: packages/agent/src/api/server.ts:4147,12348 · packages/app-core/src/api/server-config-filter.ts
    • Fix prompt: "Add cloud.apiKey to a post-redaction strip list in redactConfigSecrets, or add a unit test asserting GET /api/config never returns a non-empty cloud.apiKey value."
  • ISS-02: Add a startup warning (or hard requirement) when MILADY_API_BIND is non-loopback and no API token is set. Consider generating a random token and printing it to stdout on first Docker run.

    • Files: Dockerfile:173 · packages/agent/src/api/server.ts:6153-6167 (token generation warning block)
    • Fix prompt: "In the MILADY_API_BIND non-loopback warning block, also log a warning if no token is configured, and consider making MILADY_API_TOKEN a required ENV in the Dockerfile with a placeholder comment."

P2 — Fix in next sprint

  • ISS-03: Move GET /api/secrets behind a stricter auth check (require token even in loopback mode), or filter the response to omit isSet and maskedValue fields when no auth token is present. Metadata about which private keys are loaded is sensitive.

    • Files: packages/agent/src/api/server.ts:9699
    • Fix prompt: "Require isAuthorized(req) for /api/secrets regardless of loopback status, or strip isSet/maskedValue from the response for unauthenticated callers."
  • ISS-04: Require auth for GET /api/database/tables. This endpoint exposes the full DB schema including all table and column definitions.

    • Files: packages/agent/src/api/server.ts (database tables route)
    • Fix prompt: "Add an explicit isAuthorized check before the database/tables handler, or move it behind the existing auth gate."
  • ISS-05: Rename or split the failed field in /api/health to distinguish disabled-but-healthy plugins from actual load failures. Use {"loaded": N, "disabled": M, "errored": K} shape.

    • Files: packages/agent/src/api/server.ts:8406-8460
    • Fix prompt: "Change the health response plugin shape to {loaded, disabled, errored} so monitoring tools can distinguish disabled plugins from actual errors."

P3 — Fix when convenient

  • ISS-06: Add aria-label to the <nav> element in Header.tsx:326.

    • Fix: <nav aria-label={t("aria.primaryNav")} className="...">
  • ISS-07: Add aria-current="page" to the active nav button in desktop and mobile nav loops in Header.tsx:338,448.

    • Fix: aria-current={isActive ? "page" : undefined}
  • ISS-08: Require auth for GET /api/permissions or remove platform/shell-enabled fields from unauthenticated responses.

    • Files: packages/agent/src/api/server.ts (permissions route)

Positive Security Findings (Do-Not-Regress)

  • GET /api/wallet/keys returns HTTP 403 post-onboarding — private keys are not exposed through the keys route.
  • resolveWalletExportRejection in server-wallet-trade.ts overrides the upstream with rate limiting + forced confirmation delay.
  • isBlockedObjectKey blocks __proto__, constructor, prototype, and $include — prototype pollution and config-include path-traversal are defended.
  • SENSITIVE_ENV_RESPONSE_KEYS in server-config-filter.ts covers wallet private keys, auth tokens, and DB URLs in the env block.
  • SENSITIVE_KEY_RE in server.ts:4147 is a recursive redaction regex — covers broad key patterns without manual enumeration.
  • BLOCKED_MCP_ENV_KEYS blocks LD_PRELOAD, DYLD_INSERT_LIBRARIES, NODE_OPTIONS — prevents library injection via MCP server env config.
  • ✅ Mobile menu dialog has correct role="dialog", aria-modal="true", and aria-label — screen reader accessible.
  • ✅ Mobile nav hamburger has aria-label + aria-expanded — communicates open/closed state.
  • ✅ Touch targets on mobile nav buttons are min-h-[48px] — meets WCAG 2.5.5.
  • OnboardingPanel uses responsive breakpoints and dvh units for safe area handling on mobile.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions