Fix security bugs and migrate image uploads to /api/v1/attachments
* Replace /api/v1/resources with /api/v1/attachments for image uploads
* Upload attachments as JSON with base64-encoded content field
* After memo creation, link each attachment via PATCH /api/v1/attachments/{id}
* Rewrite markdown image URLs to use /file/attachments/{id} pattern
* Fix XSS: sanitize marked.parse output with a DOM-based allowlist sanitizer
* Fix SSRF: validate img.src scheme (http/https only) before fetching
* Fix stack overflow: use chunked base64 encoding for large images
* Update CLAUDE.md to document new attachment flow
This commit is contained in:
@@ -28,16 +28,10 @@
|
||||
|
||||
<!-- ── Main editor ── -->
|
||||
<div id="view-main" class="view hidden">
|
||||
<header class="flex items-center justify-between p-3 border-b border-gray-100 bg-white sticky top-0 z-10">
|
||||
<header class="flex items-center justify-between p-3 border-b border-gray-100 dark:border-gray-800 bg-white dark:bg-[#1a1a1f] sticky top-0 z-10">
|
||||
<div class="logo flex items-center space-x-2 text-emerald-500">
|
||||
<svg viewBox="0 0 32 32" fill="none" width="18" height="18" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="8" fill="currentColor"/>
|
||||
<path d="M11 9C11 8.44772 11.4477 8 12 8H20C20.5523 8 21 8.44772 21 9V23C21 23.5523 20.5523 24 20 24H12C11.4477 24 11 23.5523 11 23V9Z" fill="white"/>
|
||||
<path d="M14 12H18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M14 15H18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M14 18H16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span id="page-title" class="page-title font-semibold text-gray-800 truncate max-w-[180px]">Clip to Memos</span>
|
||||
<img src="icons/icon32.png" width="18" height="18" alt="Memos Logo">
|
||||
<span id="page-title" class="page-title font-semibold text-gray-800 dark:text-gray-200 truncate max-w-[180px]">Clip to Memos</span>
|
||||
</div>
|
||||
<div class="header-actions flex space-x-1">
|
||||
<button id="mode-toggle" class="icon-btn p-1.5 rounded hover:bg-gray-100 text-gray-500 transition" title="Switch clip mode">
|
||||
@@ -62,14 +56,14 @@
|
||||
</div>
|
||||
|
||||
<!-- edit panel -->
|
||||
<div id="tab-edit" class="tab-panel flex flex-col h-96 border-b border-gray-100">
|
||||
<textarea rows="14" id="md-editor" spellcheck="false" placeholder="Markdown content…" class="flex-1 w-full p-3 text-sm resize-none focus:outline-none focus:ring-1 focus:ring-emerald-100"></textarea>
|
||||
<div id="tab-edit" class="tab-panel flex flex-col h-96 border-b border-gray-100 dark:border-gray-800">
|
||||
<textarea rows="14" id="md-editor" spellcheck="false" placeholder="Markdown content…" class="flex-1 w-full p-3 text-sm resize-none focus:outline-none focus:ring-1 focus:ring-emerald-100 dark:focus:ring-emerald-900/30 bg-transparent text-inherit"></textarea>
|
||||
<div id="char-counter" class="char-counter text-right px-3 py-1 text-[10px] text-gray-400">0 chars</div>
|
||||
</div>
|
||||
|
||||
<!-- preview panel -->
|
||||
<div id="tab-preview" class="tab-panel hidden h-64 overflow-y-auto p-3 border-b border-gray-100">
|
||||
<div id="md-preview" class="preview-body prose prose-sm max-w-none"></div>
|
||||
<div id="tab-preview" class="tab-panel hidden h-64 overflow-y-auto p-3 border-b border-gray-100 dark:border-gray-800">
|
||||
<div id="md-preview" class="preview-body prose prose-sm dark:prose-invert max-w-none"></div>
|
||||
</div>
|
||||
|
||||
<!-- images section -->
|
||||
@@ -90,16 +84,16 @@
|
||||
</div>
|
||||
|
||||
<!-- footer -->
|
||||
<footer class="flex items-center justify-between p-3 bg-gray-50">
|
||||
<footer class="flex items-center justify-between p-3 bg-gray-50 dark:bg-[#22222a]">
|
||||
<div class="footer-left">
|
||||
<select id="visibility-select" class="text-xs border border-gray-200 rounded px-2 py-1 bg-white focus:outline-none focus:ring-1 focus:ring-emerald-400 transition">
|
||||
<select id="visibility-select" class="text-xs border border-gray-200 dark:border-gray-700 rounded px-2 py-1 bg-white dark:bg-[#1a1a1f] text-inherit focus:outline-none focus:ring-1 focus:ring-emerald-400 transition">
|
||||
<option value="PRIVATE">🔒 Private</option>
|
||||
<option value="PROTECTED">🔗 Protected</option>
|
||||
<option value="PUBLIC">🌐 Public</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="footer-right flex space-x-2">
|
||||
<button id="reload-btn" class="secondary-btn p-1.5 border border-gray-200 rounded hover:bg-white transition text-gray-500" title="Re-clip page">↺</button>
|
||||
<button id="reload-btn" class="secondary-btn p-1.5 border border-gray-200 dark:border-gray-700 rounded hover:bg-white dark:hover:bg-gray-800 transition text-gray-500" title="Re-clip page">↺</button>
|
||||
<button id="send-btn" class="send-btn bg-emerald-500 hover:bg-emerald-600 text-white px-4 py-1.5 rounded shadow flex items-center space-x-2 transition">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14">
|
||||
<line x1="22" y1="2" x2="11" y2="13"/>
|
||||
|
||||
Reference in New Issue
Block a user