Skip to content

feat: expose RemNote-managed image metadata#44

Merged
robert7 merged 1 commit into
robert7:mainfrom
charleseze356:feat/media-images-mvp
Jul 17, 2026
Merged

feat: expose RemNote-managed image metadata#44
robert7 merged 1 commit into
robert7:mainfrom
charleseze356:feat/media-images-mvp

Conversation

@charleseze356

@charleseze356 charleseze356 commented Jul 13, 2026

Copy link
Copy Markdown

Expose RemNote-managed image metadata

What
Note reads now include ordered, read-only metadata for embedded image attachments (stable IDs, type, position), so MCP clients can discover images instead of seeing opaque image.png placeholders. Pairs with the server's remnote_get_media (capability media.images.v1).

Scope

  • Images only, RemNote-managed files.
  • Read-only — no media modification or deletion.
  • External fetching not included.

Testing

  • 344 unit tests pass; typecheck, lint, and production build pass.
  • Live-tested a 1.31 MB pasted PNG end-to-end (metadata → retrieval as MCP-native image).

Sync
Must ship together with server PR #27 — same capability media.images.v1. A mismatched bridge/server pair won't expose media.

Rollback
The v0.16 bridge is retained (disabled); reverting = re-enabling it.


Original generated description

Summary

  • add opt-in ordered root image metadata to read_note
  • add stable media IDs and stale-reference validation
  • add capability-gated get_media_locator for safe local filename tokens
  • advertise media.images.v1 during bridge negotiation

Safety

  • never returns absolute paths or bytes from the sandboxed bridge
  • rejects external URLs, encoded separators, traversal, malformed encoding, NULs, and non-normalized tokens
  • retrieval re-reads the Rem and recomputes the media ID

Validation

  • npm run typecheck
  • npm test (344 tests)
  • npm run lint
  • npm run format:check
  • npm run build
  • live-tested against a pasted RemNote screenshot with the companion server PR

Companion PR

Requires the synchronized remnote-mcp-server media retrieval PR.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces opt-in ordered image metadata extraction for read_note and a new capability-gated get_media_locator action to resolve stable media IDs to validated local filename tokens. It also advertises the media.images.v1 capability during WebSocket negotiation. A high-severity issue was identified in getLocalToken where the strict comparison decoded !== token incorrectly rejects valid percent-encoded filenames (such as those containing spaces); performing safety checks on the decoded token instead is recommended to resolve this.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/api/rem-adapter.ts
Comment on lines +759 to +786
private getLocalToken(locator: string): string {
if (!locator.startsWith(LOCAL_FILE_PREFIX)) {
throw new Error('Unsupported media locator: image is not RemNote-managed local media');
}

const token = locator.slice(LOCAL_FILE_PREFIX.length);
let decoded: string;
try {
decoded = decodeURIComponent(token);
} catch {
throw new Error('Media path traversal rejected: invalid local media token encoding');
}

if (
!token ||
decoded !== token ||
token.normalize('NFC') !== token ||
token === '.' ||
token === '..' ||
token.includes('/') ||
token.includes('\\') ||
token.includes('\u0000')
) {
throw new Error('Media path traversal rejected: local media token must be a basename');
}

return token;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of getLocalToken rejects any local media token that contains percent-encoded characters (such as %20 for spaces) because of the strict decoded !== token check. This will cause get_media_locator to fail for any RemNote-managed local images whose filenames contain spaces or other characters that are naturally percent-encoded in the rich-text URL.

To fix this while maintaining robust security against path traversal, the safety checks should be performed on the decoded token rather than the raw encoded token, and the decoded token should be returned so the companion server can correctly locate the file on disk.

  private getLocalToken(locator: string): string {
    if (!locator.startsWith(LOCAL_FILE_PREFIX)) {
      throw new Error('Unsupported media locator: image is not RemNote-managed local media');
    }

    const token = locator.slice(LOCAL_FILE_PREFIX.length);
    let decoded: string;
    try {
      decoded = decodeURIComponent(token);
    } catch {
      throw new Error('Media path traversal rejected: invalid local media token encoding');
    }

    if (
      !decoded ||
      decoded.normalize('NFC') !== decoded ||
      decoded === '.' ||
      decoded === '..' ||
      decoded.includes('/') ||
      decoded.includes('\\') ||
      decoded.includes('\u0000')
    ) {
      throw new Error('Media path traversal rejected: local media token must be a basename');
    }

    return decoded;
  }

@charleseze356
charleseze356 marked this pull request as ready for review July 13, 2026 01:09
@robert7

robert7 commented Jul 13, 2026

Copy link
Copy Markdown
Owner

thx! I'll check it later...

@robert7
robert7 merged commit 8c78e13 into robert7:main Jul 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants