Obsidian Yt Archiver
Scan Obsidian vault for YouTube links, archive in TubeArchivist, generate reference notes
2026.09.19.2
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
Global Arguments
| Argument | Type | Description |
|---|---|---|
| vaultPath | string | Absolute path to Obsidian vault |
| Argument | Type | Description |
|---|---|---|
| folder? | string | Subfolder to scan (relative to vault root) |
| Argument | Type | Description |
|---|---|---|
| folder? | string | Subfolder to scan |
Resources
2026.09.17.1
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.08.02.1
Fixes the remaining seven latent bugs -- LB1, LB3, LB4, LB5, LB6, LB7, LB8 --
tracked in the LOCAL obsidian-yt-archiver-latent-bugs issue-lifecycle model
(NEVER filed to the swamp.club Lab -- see CLAUDE.md's anti-bypass rule). LB2 was
already fixed in 2026.08.01.1 and is untouched here. No new npm/jsr dependency
was added (jsr:@std/path@1 remains the only non-builtin import; deno.lock is
unchanged) and no resource schema or globalArguments shape changed -- the
version bump is a pure identity upgrade.
- LB1 (MEDIUM, path traversal / request-forgery via
videoIds): added a sharedtaVideoPath(id)helper (encodeURIComponent) used at all three GET-path build sites (archive/resolve/sync). A../-laden or absolute-URL-shaped id is now percent-encoded into ONE opaque/api/video/<id>/path segment instead of reaching a different TA endpoint on the same host. Identity for every benign id used across the suites ([A-Za-z0-9_-]) -- every byte-frozen contract/methods URL pin stays unchanged. - LB3 (MEDIUM, conflated error handling -> mass re-queue):
taApinow throws a typedTaHttpErrorcarrying.statuson any non-2xx/redirect/ non-JSON response. A sharedisNotArchived(e)predicate treats ONLY a genuine 404 as "not archived" (queue/unresolved); every other failure (401/403/500/502/503/timeout/network/redirect/non-JSON) is re-thrown and surfaces instead of being silently re-queued alongside real not-archived ids. - LB4 (MEDIUM, no fetch timeout): every
taApicall now passes anAbortController-backedsignalwith a 30s default timeout (DEFAULT_REQUEST_TIMEOUT_MS), cleared in a singlefinallyso no timer ever leaks (satisfies Deno's op-sanitizer on every code path: success, redirect-throw,!ok-throw, non-JSON-throw, network-throw). - LB5 (LOW-MED, unbounded sequential per-id fetch): a shared
assertVideoIdCap(ids)REJECTS (never silently slices/drops) any id list longer thanMAX_VIDEO_IDS = 500, checked once per method immediately after the id list is resolved (archive/resolve/sync), before any fetch fires. Sequential per-id execution order is unchanged -- no batching/concurrency was introduced. - LB6 (LOW, error body truncated to 200 raw chars): replaced the raw
200-char slice with
redactBody()-- collapses whitespace runs and caps the result at 120 chars (plus an ellipsis) before it is interpolated into any thrown message. The auth token was already header-only and remains never part of this text. - LB7 (LOW, default
redirect:"follow"):taApinow passesredirect: "manual"on every call, plus an explicit guard that throws on any 3xx status or an"opaqueredirect"response type (surfaced, never silently followed to a possibly-different host) and a defense-in-depth host-revalidation check against the operator-configuredtubearchivistUrl. - LB8 (LOW, non-JSON 200 -> blank "archived" record):
taApigained anexpectJsonparameter (defaulttrue). Every per-id metadata GET check now surfaces (throws) on a 2xx response whose content-type is notapplication/json, instead of silently returning{}and recording a blankarchived: trueentry. The two fire-and-forget POST calls (/api/download/,download_pending) passexpectJson: falseand keep their existing{}-on-non-JSON behavior -- zero POST-shape regression. - Test suites: flipped every LB1/LB3/LB4/LB5/LB6/LB7/LB8 characterization
pin in
obsidian_yt_archiver_adversarial_test.tsfrom "the bug is present" tofixed (... FIXED)(mostlyassertRejects), added new cases (LB3's 401/500 split, LB5's 501-id cap-reject, LB7's 302-reject), and flipped the coverage suite's no-content-type-header-200 test toassertRejects(LB8). Rewrote the property suite's(b)/(b-resolve)"archive()/ resolve() never throw for ANY status" properties -- directly contradicted by the LB3/LB7/LB8 fixes -- into a(b1)never-throws property scoped to {genuine JSON-200, genuine 404} and a NEW(b2)surfaces property scoped to {redirect, 4xx/5xx, non-JSON 200}, partitioning every GET-check outcome the suite generates with no overlap. LB2's fix and every byte-frozen contract/methods pin stay green, unchanged. - Adversarial + security review follow-up: added two coverage-closing tests
the reviews flagged as untested claims -- a raw network-level failure
(
fetch()itself rejecting, not an HTTP error response) surfacing correctly through the LB3 catch/rethrow chain, and the LB7 host-revalidation defense-in-depth branch (previously unreachable by any stub, since a directly-constructedResponsealways leavesurlempty -- now exercised viaObject.definePropertyshadowingres.url). manifest.yamlandmodel.versionbumped to2026.08.02.1.
2026.08.01.1
Fixes LB2 (HIGH, path traversal), tracked in the LOCAL
obsidian-yt-archiver-latent-bugs issue-lifecycle model (NEVER filed to the
swamp.club Lab -- see CLAUDE.md's anti-bypass rule). scan's and sync's
folder method-argument was concatenated directly into vaultPath with no
containment check, so folder: "../outside" (or a deeper/absolute escape)
walked arbitrary host directories via walkMd and read every non-hidden .md
file outside the vault.
- Added a shared pure guard,
assertFolderWithinVault(vaultPath, folder), usingjsr:@std/path@1'sresolve/relative/isAbsolute-- LEXICAL only (neverDeno.realPath, since the vault commonly lives under a symlinked temp root, e.g. macOS/varresolving to/private/var, and realPath would break every legitimate scan of such a vault). Rejects an absolutefolder, or a vault-relative resolved path that is..or begins with../. Invoked identically at bothscan.executeandsync.execute, beforewalkMdruns -- one shared helper, no divergent inline re-check. - Flipped the LB2 characterization pin in
obsidian_yt_archiver_adversarial_test.tsfrom "the escape succeeds" toassertRejects, for bothscanandsync, and added deeper rejection cases (..,../..,notes/../../outside, and an absolute path) for both methods. Every previously-green legit-subfolder test (scan/syncfolder=notes/Clippings/Sub) and the does-not-exist ->Deno.errors.NotFoundtest stay green: a contained-but-nonexistent folder passes the guard and still fails atreadDir, unchanged. - Added the
jsr:@std/path@1dependency;deno.lockregenerated on deno 2.8.3. No other runtime behavior changes -- LB1 and LB3-LB8 remain latent/tracked in the same local model, unaffected by this change. manifest.yamlandmodel.versionbumped to2026.08.01.1.
Release 2026.07.16.2 — align model versions with manifests
Maintenance release across the @magistr extensions. For most packages this
carries no functional change: the only edit is the model's version: field,
brought back in line with its manifest version so the published model type
version and the package version no longer drift.
Functional changes in this release are limited to:
anime-cron: normalizeTitle now strips a ": subtitle" suffix and a trailing parenthesized year before comparison, fixing dedup false-misses where the torrent title carries a subtitle or year that the AniList romaji does not.
arckit: first publish. Standalone ArcKit port — a 12-phase architecture governance state machine with 65 bundled templates, driven by a bundled skill.
Also tracks three extensions (kaiten, observability-agent, music-library) that previously existed only as untracked working-tree directories, recovered from stashes.
Merge pull request #4 from umag/extensions/magistr-grade-a-workspace
extensions: stage 15 @magistr extensions as Grade A workspace dirs + wire CI
- Has README or module doc2/2earned
- README has a code example1/1earned
- README is substantive1/1earned
- Most symbols documented1/1earned
- No slow types (deprecated)1/1earned
- Dependencies pass trust audit2/2earned
- Has description1/1earned
- Platform support declared (or universal)2/2earned
- License declared1/1earned
- Verified public repository2/2earned