Detailed the (now-fixed) SSRF/injection attack paths; no need to publish
it on a plugin heading for store review. Kept in git history and locally.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
new URL() keeps the trailing dot on FQDN hostnames ('localhost.',
'printer.local.') while stripping it from IPv4, so those slipped past
the loopback/.local literal checks. Strip one trailing dot before the
compares. Found in final whole-branch review.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A client disconnect mid-POST emits 'error' on the request stream; with
no listener Node rethrew it as an uncaught exception (renderer console
noise). Added a no-op error listener — the socket is already gone, so
there is nothing to respond to.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
restartServer now rebinds inside close()'s callback instead of the same
tick, closing the close->listen race that produced EADDRINUSE. A failed
listen (EADDRINUSE or otherwise) nulls the server reference and shows a
Notice, so /ping and clips stop silently pretending the server is up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The port onChange only saved the value; the server kept listening on the
old port until Obsidian restarted. It now restarts so a port change takes
effect immediately (no unit test — settings UI has none per convention;
verified by reading + tsc).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The coercion itself landed in the injection-hardening commit (inlineText
coerces null/undefined to ''); this pins the behavior as a regression.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
/shorts/<id> and /live/<id> now extract the id (instead of falling back
to the full URL), so the same video clipped from a Shorts or Live URL
merges into one note and gets its gallery cover. Hook mode is exactly
the Shorts use case.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
platform/published now go through yamlString (a crafted value could
inject frontmatter keys; a legit value with a colon broke parsing).
Untrusted titles are newline/backtick-stripped before body headings,
transcripts backtick-stripped (with Dataview JS enabled an injected
dataviewjs fence executes on render). Filenames also drop backticks.
clip-validate gains type-only checks for optional string fields —
verified against the extension contract tests, which pass unmodified.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The CSRF-reachable /clip endpoint let a malicious page point
thumbnail_url at localhost, LAN, or cloud-metadata addresses and write
the response into the vault. assertDownloadable now blocks literal
loopback/private/link-local hosts (IPv4 incl. parser-normalized hex
forms, IPv6 incl. v4-mapped, localhost, .local). DNS-rebinding
protection deliberately out of scope.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Double-clicks and extension retries interleaved read-modify-write on the
same note (last writer dropped the other's section) and raced
vault.create for new videos. All clips now run through a serial queue;
a failed clip still answers its own request and never blocks the next.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Obsidian's Properties editor re-serializes frontmatter (drops quotes,
rewrites inline lists to block style), which broke the exact-substring
video_id match and the addDimension regex — every later clip created a
permanent duplicate note. Cache misses (just-created notes) fall back
to a quote-tolerant scan; cached non-matches skip the file read.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Buffers under ~4 KB are views into Node's shared 8 KB pool; passing
.buffer raw wrote the whole pool (with unrelated memory) as the image.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Titles like '[Official Video]' or 'C#' produced names Obsidian rejects
or that break the ![[...]] embed syntax.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unrecognized ## headings no longer fall back to kind='motion' (which
rewrote the heading text and re-sorted the section). Unknown sections
keep their exact text and sort after all known sections.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Community plugin review requires theme-aware colors. Also document why
SOP loading uses Node fs instead of the Vault API (absolute paths may
point outside the vault).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All 3 SOPs (cover/hook/keyframe, zh+en) updated to the new point-at-things
analysis framework. Vault frontmatter stripped; en-dashes in EN copies
replaced with hyphens to satisfy the no-dash style test.
The aspect-ratio rule only matched reading-view containers
(.markdown-preview-view / .markdown-rendered), so in Live Preview the
iframe fell back to its fixed height="315" and rendered squashed.
Add .markdown-source-view to cover CM6 html embeds.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Empty sopPath now falls back to the mode's bundled SOP (master switch
useBuiltinSops, default on); a filled path always wins, and a broken
path falls back to built-in instead of dropping the analysis block.
- Settings: Analysis SOPs section with per-row state line (built-in /
custom / off) and a Customize button that forks the built-in into
<base>/SOPs and points the path at the copy. Install-all button removed.
- sopPath accepts vault-relative paths (readFileSync resolves them).
- Bundled SOP files carry no personal vault frontmatter (regression test).
- All strings bilingual (en + zh).