-
Notifications
You must be signed in to change notification settings - Fork 60
Description
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 devcommand — impressively fast for a full runtime + DB init + plugin loading stack. CLAUDE.mdis 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;
AgentSkillscatalog is functional immediately. - CLI entry and dev orchestrator are cleanly separated (
entry.ts→dev-ui.mjs).
Dislikes:
GET /api/configreturns the livecloud.apiKeyin plaintext (ISS-01). A dev who forwards port 31337 for remote testing inadvertently exposes their cloud credentials.- 86 plugins listed as "failed" in
/api/healthis alarming for first-time operators — these are disabled plugins, not actual load failures (ISS-05). ELIZA_DEV_ONCHAIN=0must 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/config→cloud.apiKeyreturned in plaintext. Code atpackages/agent/src/api/server.ts:12348callsredactConfigSecrets(state.config). TheredactDeepfunction at line 4217 appliesSENSITIVE_KEY_RE(/api.?key/i) recursively; the keyapiKeyshould 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 intofailedcount, creating false alarm signal.
2. Maria (Non-Technical Creator) ⭐⭐⭐ 3/5
Likes:
GET /api/onboarding/statusreturns{"complete":true}in ~5ms — onboarding state is persisted reliably.CloudOnboardingcomponent (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.
OnboardingPanelstep transitions are animated (onboarding-panel-enter) but the animation usesvoid panel.offsetHeightforce-reflow hacks (OnboardingPanel.tsx:17) — can cause jank on low-end devices.- Error message in
CloudOnboardingonly 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/.miladyensures state survives container restarts when/datais a volume.Railwayport env var (PORT) is handled automatically:CMD … MILADY_PORT=${PORT:-2138}.bun installis idempotent ("Checked 1758 installs across 2003 packages (no changes)").
Dislikes:
- ISS-02 (P1):
Dockerfile:173setsENV MILADY_API_BIND="0.0.0.0"but no defaultMILADY_API_TOKENorELIZA_API_TOKENis set.isAuthorized()atpackages/agent/src/api/server.ts:6169returnstruewhen no token is configured — entire API is open on all interfaces in container deployments without operator action. - No
EXPOSEdirective in Dockerfile — container port mapping is implicit, making it harder to self-document expected ports. ELIZA_DEV_ONCHAIN=0is 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 to0.0.0.0unauthenticated by default.
4. Sam (Crypto-Native Power User) ⭐⭐⭐⭐ 4/5
Likes:
GET /api/wallet/keyscorrectly 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
resolveWalletExportRejectioninpackages/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_KEYandEVM_PRIVATE_KEYare listed inSENSITIVE_ENV_RESPONSE_KEYSinserver-config-filter.ts:12,15— filtered fromenvblock of config responses.
Dislikes:
GET /api/secrets(unauthenticated) revealsSOLANA_PRIVATE_KEY: {isSet: true, maskedValue: "474P...Y5P1"}andEVM_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/configreturnscloud.apiKeyin 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:9699—GET /api/secretsunauthenticated, 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
isBlockedObjectKeychecking__proto__,constructor,prototype, and$include(packages/agent/src/api/server.ts:4154-4161). - Audit log system exists (
packages/agent/src/security/audit-log.ts—AUDIT_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.0with 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-workspacesymlinks local../eliza— live package development without publish cycles.scripts/patch-deps.mjsauto-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/healthwill confuse plugin devs who check health after their plugin installs — impossible to tell signal from noise (ISS-05). stream-browserifyVite 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=0must 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 (
Menuicon,sm:hiddenbreakpoint) 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). OnboardingPanelusesmax-md:breakpoints throughout and hasmax-md:max-h-[min(60dvh,calc(100dvh-8.5rem))]for safe area handling.- Header layout uses
px-5 sm:px-8responsive padding — adapts cleanly between breakpoints. - Mobile overlay uses
w-[min(22rem,88vw)]— won't overflow narrow viewports.
Dislikes:
- Desktop nav (
<nav>) ishidden 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-autoon 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")}andaria-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 ines.json).data-testidattributes on nav buttons enable reliable automated testing.
Dislikes:
- ISS-06 (P3): The desktop
<nav>element atHeader.tsx:326has noaria-labelattribute. 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) andvariant="default"to signal active state, but noaria-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-currenteither (Header.tsx:444-462).
Issues found:
- ISS-06 (P3):
packages/app-core/src/components/Header.tsx:326—<nav>missingaria-label. - ISS-07 (P3):
packages/app-core/src/components/Header.tsx:338,448— active nav buttons missingaria-current="page".
9. Jamie (Multi-Platform User) ⭐⭐⭐ 3/5
Likes:
- Connector health system exists (
packages/app-core/src/api/connector-health.ts). GET /api/connectorsreturns{"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/statusreturns{"complete":true}— returning users skip onboarding immediately.CloudOnboardingcomponent 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:2138directly has no context — no product description, no "what is this?" explanation visible inCloudOnboarding.tsx(just "Connect to Eliza Cloud to get started"). - The
CloudOnboardingerror 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/keysrequires onboarding context — returns HTTP 403 post-onboarding. Not a route gap; intentional lifecycle gating.GET /api/walletreturns 404 — route does not exist. Wallet state is accessed via sub-routes (/keys, export flow). Not a regression.isAuthorizedopen-by-default — documented behaviour. WhenMILADY_API_TOKEN/ELIZA_API_TOKENis set, all routes (except/api/auth/*and/api/onboarding/status) are properly gated atpackages/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_KEYSmissingcloud.apiKey—server-config-filter.ts:filterConfigEnvForResponseonly filters theenvblock. Thecloud.apiKeyfield is in thecloudblock, andredactDeepinserver.ts:4217usesSENSITIVE_KEY_RE(/api.?key/i) which should matchapiKey. 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-browserifyVite 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.apiKeyis returned in plaintext fromGET /api/configdespiteredactDeepapplyingSENSITIVE_KEY_RE. Add an explicit guard inredactConfigSecretsor infilterConfigEnvForResponseto stripcloud.apiKeyfrom API responses.- Files:
packages/agent/src/api/server.ts:4147,12348·packages/app-core/src/api/server-config-filter.ts - Fix prompt: "Add
cloud.apiKeyto a post-redaction strip list inredactConfigSecrets, or add a unit test assertingGET /api/confignever returns a non-emptycloud.apiKeyvalue."
- Files:
-
ISS-02: Add a startup warning (or hard requirement) when
MILADY_API_BINDis 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_BINDnon-loopback warning block, also log a warning if no token is configured, and consider makingMILADY_API_TOKENa required ENV in the Dockerfile with a placeholder comment."
- Files:
P2 — Fix in next sprint
-
ISS-03: Move
GET /api/secretsbehind a stricter auth check (require token even in loopback mode), or filter the response to omitisSetandmaskedValuefields 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/secretsregardless of loopback status, or stripisSet/maskedValuefrom the response for unauthenticated callers."
- Files:
-
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
isAuthorizedcheck before the database/tables handler, or move it behind the existing auth gate."
- Files:
-
ISS-05: Rename or split the
failedfield in/api/healthto 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."
- Files:
P3 — Fix when convenient
-
ISS-06: Add
aria-labelto the<nav>element inHeader.tsx:326.- Fix:
<nav aria-label={t("aria.primaryNav")} className="...">
- Fix:
-
ISS-07: Add
aria-current="page"to the active nav button in desktop and mobile nav loops inHeader.tsx:338,448.- Fix:
aria-current={isActive ? "page" : undefined}
- Fix:
-
ISS-08: Require auth for
GET /api/permissionsor remove platform/shell-enabled fields from unauthenticated responses.- Files:
packages/agent/src/api/server.ts(permissions route)
- Files:
Positive Security Findings (Do-Not-Regress)
- ✅
GET /api/wallet/keysreturns HTTP 403 post-onboarding — private keys are not exposed through the keys route. - ✅
resolveWalletExportRejectioninserver-wallet-trade.tsoverrides the upstream with rate limiting + forced confirmation delay. - ✅
isBlockedObjectKeyblocks__proto__,constructor,prototype, and$include— prototype pollution and config-include path-traversal are defended. - ✅
SENSITIVE_ENV_RESPONSE_KEYSinserver-config-filter.tscovers wallet private keys, auth tokens, and DB URLs in theenvblock. - ✅
SENSITIVE_KEY_REinserver.ts:4147is a recursive redaction regex — covers broad key patterns without manual enumeration. - ✅
BLOCKED_MCP_ENV_KEYSblocksLD_PRELOAD,DYLD_INSERT_LIBRARIES,NODE_OPTIONS— prevents library injection via MCP server env config. - ✅ Mobile menu dialog has correct
role="dialog",aria-modal="true", andaria-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. - ✅
OnboardingPaneluses responsive breakpoints anddvhunits for safe area handling on mobile.