feat: expose RemNote-managed image metadata#44
Conversation
There was a problem hiding this comment.
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.
| 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; | ||
| } |
There was a problem hiding this comment.
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;
}|
thx! I'll check it later... |
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.pngplaceholders. Pairs with the server'sremnote_get_media(capabilitymedia.images.v1).Scope
Testing
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
read_noteget_media_locatorfor safe local filename tokensmedia.images.v1during bridge negotiationSafety
Validation
npm run typechecknpm test(344 tests)npm run lintnpm run format:checknpm run buildCompanion PR
Requires the synchronized
remnote-mcp-servermedia retrieval PR.