Describe the bug
explicit-publish keeps unpublished notes off the site, but attachments belonging to those notes are still copied into public/ and served. A vault used with explicit-publish therefore publishes every image, PDF and video it contains, including those only ever referenced from notes marked publish: false.
This is independent of encrypted-pages — the asset is emitted with that plugin disabled entirely, so the cause is explicit-publish alone. But the two together make the gap wider: pages are encrypted, attachments are not, so the files are readable without the password that gates everything else.
I do not think this is a misconfiguration — the Assets plugin docs say it emits "all non-Markdown static assets in your content folder" and has no configuration options. But when a filter plugin is enabled, "all" is probably not what the user is asking for.
To Reproduce
Two files and an image:
content/published.md
---
title: Published Note
publish: true
---
This note is published.
content/private-note.md
---
title: Private Note
publish: false
---
This note is not published. But the image below is.
![[secret-diagram.png]]
content/secret-diagram.png (any image)
With ExplicitPublish enabled and no other plugins required, run npx quartz build and look at public/:
published.html ✓ expected
private-note.html ✓ correctly absent
secret-diagram.png ✗ present
The image is copied even though the only note referencing it was filtered out.
Expected behavior
An attachment that is only referenced by filtered-out content should not be emitted. Either of these would do it:
- Copy only assets referenced by pages that survived filtering.
- Skip assets whose referencing notes were all filtered out.
The first is stricter and also removes assets nothing references at all.
Screenshots and Source
The relevant code is quartz/plugins/emitters/assets.ts:
const filesToCopy = async (argv: Argv, cfg: QuartzConfig, excludeExtensions: Set<string>) => {
const excludePatterns = ["**/*.md", ...cfg.configuration.ignorePatterns]
for (const ext of excludeExtensions) {
excludePatterns.push(`**/*${ext}`)
}
return await glob("**", argv.directory, excludePatterns)
}
It globs the content directory directly, so it never sees the filtered content and cannot know which assets survived. emit(ctx) also ignores the content argument that other emitters use.
filterContent runs before emitContent (quartz/build.ts:95), so by emit time the filtered set is available — an emitter that walked those pages for img, video, audio and iframe sources would have exactly the referenced set. Happy to attempt a PR along those lines if that direction seems right.
Two things worth noting for whoever picks this up:
- Image references are not in
file.data.links. crawl-links rewrites node.properties.src but only pushes <a href> targets into data.links, so the tree has to be walked directly.
Assets() is registered as a builtin in quartz/plugins/loader/config-loader.ts, so its behaviour can't currently be changed from quartz.config.yaml.
Environment
- Quartz v5.0.0
- Node v26
- macOS
Impact
For a public digital garden this is mostly wasted bandwidth. For anyone using explicit-publish to publish a subset of a private vault, every attachment in that vault is on the internet. Nothing in the configuration hints at it, and the files are served unencrypted even when encrypted-pages is on.
Describe the bug
explicit-publishkeeps unpublished notes off the site, but attachments belonging to those notes are still copied intopublic/and served. A vault used withexplicit-publishtherefore publishes every image, PDF and video it contains, including those only ever referenced from notes markedpublish: false.This is independent of
encrypted-pages— the asset is emitted with that plugin disabled entirely, so the cause isexplicit-publishalone. But the two together make the gap wider: pages are encrypted, attachments are not, so the files are readable without the password that gates everything else.I do not think this is a misconfiguration — the Assets plugin docs say it emits "all non-Markdown static assets in your content folder" and has no configuration options. But when a filter plugin is enabled, "all" is probably not what the user is asking for.
To Reproduce
Two files and an image:
With
ExplicitPublishenabled and no other plugins required, runnpx quartz buildand look atpublic/:The image is copied even though the only note referencing it was filtered out.
Expected behavior
An attachment that is only referenced by filtered-out content should not be emitted. Either of these would do it:
The first is stricter and also removes assets nothing references at all.
Screenshots and Source
The relevant code is
quartz/plugins/emitters/assets.ts:It globs the content directory directly, so it never sees the filtered content and cannot know which assets survived.
emit(ctx)also ignores thecontentargument that other emitters use.filterContentruns beforeemitContent(quartz/build.ts:95), so by emit time the filtered set is available — an emitter that walked those pages forimg,video,audioandiframesources would have exactly the referenced set. Happy to attempt a PR along those lines if that direction seems right.Two things worth noting for whoever picks this up:
file.data.links.crawl-linksrewritesnode.properties.srcbut only pushes<a href>targets intodata.links, so the tree has to be walked directly.Assets()is registered as a builtin inquartz/plugins/loader/config-loader.ts, so its behaviour can't currently be changed fromquartz.config.yaml.Environment
Impact
For a public digital garden this is mostly wasted bandwidth. For anyone using
explicit-publishto publish a subset of a private vault, every attachment in that vault is on the internet. Nothing in the configuration hints at it, and the files are served unencrypted even whenencrypted-pagesis on.