MoonshotAI / MoonshotAI/kimi-code

web UI renders ms:// video URLs into <video src>: placeholder, unplayable, CSP-blocked under --host

Open
#3,208 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
7.5k
Forks
1.2k
Avg merge
11h 53m
Merged PRs (30d)
350

Description

Summary

In --host mode, videos shown in the kimi web UI render as a placeholder and cannot be played. The browser console reports:

Loading media from 'ms://fc833bfx9fx111cppzs1' violates the following Content Security Policy directive: "default-src 'self'". Note that 'media-src' was not explicitly set, so 'default-src' is used as a fallback. The action has been blocked.

User scenario: kimi-code started with --host; the agent runs a script that generates a video on a remote server, copies it back to a local path (e.g. /tmp/h3_fl2va_t2v.mp4), and loads it into the session. The video renders as a placeholder in the web UI and is unplayable.

Verified against kimi-code main @ dceb3fd (0.38.0) and kimi-code-app main @ 97006d3.

Root cause

In the v2 architecture, ms:// (Moonshot platform file IDs) is supposed to exist only in the in-memory copy of an LLM request built by mediaResolverService — it is never written back to history for user messages. But the ReadMediaFile tool persists provider-side ms:// references into history as tool output:

  1. agent-core-v2/src/agent/tools/read-media-file/readMediaFileTool.ts:196-213videoContentPart calls the video uploader and embeds the resulting ms:// part in the tool output ([<video path="/tmp/…">, video_url ms://…, </video>], see :458-474). This tool message is persisted into context / the wire journal.
  2. kap-server/src/services/messages/messageProjection.ts:32-37 — by design, any non-kimi-file:// video_url is passed through verbatim as {kind:'url', url} (locked in by messageProjection.test.ts:111-121); tool messages with media parts are output as-is at :46-51. This reaches the web client via GET /api/v1/sessions/{id}/messages and the transcript WS frames (services/transcript/coreEventMap.ts:838-851).
  3. Client-side last line of defense: kimi-code-app/packages/app-core/src/client/messagesToTurns.ts:315-318 tries to recover a fileId from the <video path> when the URL is ms://, but fileIdFromCachePath (:80-92) only accepts daemon cache path shapes (f_<ULID|UUID> basename). An arbitrary script-generated path like /tmp/h3_fl2va_t2v.mp4 does not match → fileId is undefined.
  4. kimi-code-app/apps/web/src/components/chat/AuthMedia.vue:34,56-61,111-120 — with no fileId, resolvedUrl = props.url is emitted verbatim, so the DOM gets <video src="ms://…">. The browser cannot load the ms:// scheme; under --host, the CSP (kap-server/src/middleware/securityHeaders.ts:9-10, applied only for non-loopback binds per start.ts:341-343) has no media-src, so default-src 'self' blocks it first. The desktop renderer (apps/desktop/.../AuthMedia.vue, byte-identical) has the same flaw, failing with an unknown-scheme error instead of CSP.

Note: the user-attachment path (attach a video to a prompt) is not affected in v2 — it stores kimi-file://<f_id> and projects to {kind:'session_media', file_id}, which streams correctly via GET /api/v1/sessions/{id}/media/{file_id} (kap-server/src/routes/sessionMedia.ts:48-110). The ms://-into-history behavior described here matches the v1 engine (agent-core/src/agent/turn/index.ts:593-599), but the leaking path on v2/--host is specifically ReadMediaFile tool results.

Aggravating finding: CSP may block even healthy video playback under --host

The CSP sets no media-src, so it falls back to default-src 'self'. The healthy path (AuthMedia authenticated fetch → blob: URL → <video>) uses a blob: src, which is not covered by 'self' — so even correctly attached videos may be CSP-blocked under --host. (Inferred from the CSP spec; not browser-verified.)

Suggested fix shape

A. Fix at the source (primary): make ReadMediaFile video results reference a servable file. In readMediaFileTool.videoContentPart, materialize the bytes into the session media store and return a kimi-file://<f_id> part instead of the provider ms:// part. Request building still resolves/uploads to ms:// via mediaResolver as today, but history only ever contains daemon-servable references. Projection and web client need zero changes (the kimi-file://session_media route already exists). Also add media-src 'self' blob: to securityHeaders.ts (one line; verify the blob-video CSP question above in a browser).

B. Server-side re-sourcing route for ms:// (proxy Moonshot platform files through kap-server): needs provider credentials, platform file TTL handling, auth/caching/failure semantics — high effort, fragile payoff. Not recommended as the primary fix.

C. Client-side graceful degradation (required regardless): in kimi-code-app (web + desktop), recognize ms:// with no recoverable fileId and render a "video unavailable (provider reference cannot be served)" placeholder instead of a bare <video src="ms://…">. Needed for existing sessions whose history already contains ms:// references that can never be re-sourced.

Recommended combination: A + C, plus the one-line CSP media-src addition.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with agent-core-v2/src/agent/tools/read-media-file/readMediaFileTool.ts, then inspect AuthMedia.vue in the web and desktop apps and kap-server/src/middleware/securityHeaders.ts. Run the referenced message projection tests and add coverage for the affected media paths. Done means persisted tool results use servable references, unresolvable ms:// media degrades gracefully, and blob video playback is allowed under --host.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
full-stack, security, web-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.