<feed xmlns='http://www.w3.org/2005/Atom'>
<title>skunky-art.git, branch v1.3.9</title>
<subtitle>alternative deviantart frontend, no javascript. go.
</subtitle>
<id>http://git.krz.sh/krz/skunky-art.git/atom?h=v1.3.9</id>
<link rel='self' href='http://git.krz.sh/krz/skunky-art.git/atom?h=v1.3.9'/>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/skunky-art.git/'/>
<updated>2026-08-07T23:49:04+00:00</updated>
<entry>
<title>fix: correct inverted media guard in sendMedia</title>
<updated>2026-08-07T23:49:04+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-08-07T23:47:01+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/skunky-art.git/commit/?id=9af155d40accef7aac3ffc6eb960d197798ebd1f'/>
<id>urn:sha1:9af155d40accef7aac3ffc6eb960d197798ebd1f</id>
<content type='text'>
sendMedia returned when the assembled URL was non-empty — i.e. for every
deviation that actually has media — so the random-media endpoint served
an empty body and never sent the image. For a media-less deviation it
instead fell through to mediaURL[21:] on an empty string, panicking in
proxy mode. Guard on len == 0 so real media is sent and absent media is
a no-op.
</content>
</entry>
<entry>
<title>fix: add blur op for mature deviations with blur-constrained tokens</title>
<updated>2026-08-07T23:45:03+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-08-07T23:34:59+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/skunky-art.git/commit/?id=ee9fc6db38dbbee7418f8a3be2ce25eaa76f90ec'/>
<id>urn:sha1:ee9fc6db38dbbee7418f8a3be2ce25eaa76f90ec</id>
<content type='text'>
Mature deviations are signed with a watermark-service token whose obj
carries a "blur": "&gt;=N" constraint. The composed wixmp /v1/fit
transform had no blur operation, so wixmp rejected it with 403, which
the proxy passed through as a broken image.

buildMediaURL now decodes the token and, when it demands a blur, appends
a matching blur_N op to the transform (e.g. w_1280,h_1920,blur_10).
Unconstrained media and tokens that don't parse are left untouched, so
only media that requires it is affected.

Closes #14
</content>
</entry>
<entry>
<title>feat: add hide-ai option to omit AI-generated deviations</title>
<updated>2026-08-07T23:11:47+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-08-07T23:09:39+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/skunky-art.git/commit/?id=ddb8aa297962379fa7731032140221ba35f84b50'/>
<id>urn:sha1:ddb8aa297962379fa7731032140221ba35f84b50</id>
<content type='text'>
Adds a hide-ai config flag that drops deviations flagged data.AI from
every listing (search, daily, galleries, favourites) in the shared
DeviationList loop, analogous to how nsfw gates mature content. The
flag is already parsed and shown as a marker, so this is a filter over
existing data. Off by default.

Closes #15
</content>
</entry>
<entry>
<title>fix(deps): bump devianter to v0.3.4</title>
<updated>2026-08-07T23:11:47+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-08-07T23:09:31+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/skunky-art.git/commit/?id=ad54fbcda1ba7f654dc27c06bf22f2628f6c7cf4'/>
<id>urn:sha1:ad54fbcda1ba7f654dc27c06bf22f2628f6c7cf4</id>
<content type='text'>
v0.3.3 was tagged before the zerolabsco-&gt;krazywarez rename landed in
its go.mod, so it declares module github.com/zerolabsco/devianter and
cannot be imported under the krazywarez path — the build fails to
resolve. v0.3.4 is cut from main with the corrected module path and no
code changes.
</content>
</entry>
<entry>
<title>convert readme to nfo; convert docs to txt</title>
<updated>2026-08-02T20:35:14+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-08-02T20:29:37+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/skunky-art.git/commit/?id=7acbd87221f6e4594edf3db029520997027a7bed'/>
<id>urn:sha1:7acbd87221f6e4594edf3db029520997027a7bed</id>
<content type='text'>
</content>
</entry>
<entry>
<title>update org name</title>
<updated>2026-08-02T13:30:01+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-08-02T06:51:22+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/skunky-art.git/commit/?id=4178e3893f310425e76f6d4515af9e7d0ea36a0a'/>
<id>urn:sha1:4178e3893f310425e76f6d4515af9e7d0ea36a0a</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Bump actions/attest-build-provenance from 2 to 4 (#10)</title>
<updated>2026-07-22T02:57:34+00:00</updated>
<author>
<name>dependabot[bot]</name>
<email>49699333+dependabot[bot]@users.noreply.github.com</email>
</author>
<published>2026-07-22T02:57:34+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/skunky-art.git/commit/?id=4dd5fd4f747c95cb258ab9a634d48f3eb8f19491'/>
<id>urn:sha1:4dd5fd4f747c95cb258ab9a634d48f3eb8f19491</id>
<content type='text'>
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 2 to 4.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/v2...v4)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] &lt;support@github.com&gt;
Co-authored-by: dependabot[bot] &lt;49699333+dependabot[bot]@users.noreply.github.com&gt;</content>
</entry>
<entry>
<title>Bump github.com/zerolabsco/devianter from 0.3.1 to 0.3.3 (#11)</title>
<updated>2026-07-22T02:56:44+00:00</updated>
<author>
<name>dependabot[bot]</name>
<email>49699333+dependabot[bot]@users.noreply.github.com</email>
</author>
<published>2026-07-22T02:56:44+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/skunky-art.git/commit/?id=a06866cf167148164c23b0c3cb2394c80f9a6727'/>
<id>urn:sha1:a06866cf167148164c23b0c3cb2394c80f9a6727</id>
<content type='text'>
Bumps [github.com/zerolabsco/devianter](https://github.com/zerolabsco/devianter) from 0.3.1 to 0.3.3.
- [Release notes](https://github.com/zerolabsco/devianter/releases)
- [Commits](https://github.com/zerolabsco/devianter/compare/v0.3.1...v0.3.3)

---
updated-dependencies:
- dependency-name: github.com/zerolabsco/devianter
  dependency-version: 0.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] &lt;support@github.com&gt;
Co-authored-by: dependabot[bot] &lt;49699333+dependabot[bot]@users.noreply.github.com&gt;</content>
</entry>
<entry>
<title>add CODEOWNERS file</title>
<updated>2026-07-15T16:44:25+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-15T16:44:25+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/skunky-art.git/commit/?id=2b9a6d0dd8608966ef70ace0f42598a6e540b459'/>
<id>urn:sha1:2b9a6d0dd8608966ef70ace0f42598a6e540b459</id>
<content type='text'>
</content>
</entry>
<entry>
<title>fix: reject forged subdomains in the media proxy (#9)</title>
<updated>2026-07-15T16:41:18+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-15T16:41:18+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/skunky-art.git/commit/?id=184ce2c6dcdd047d6888fd243b9189c797f58d38'/>
<id>urn:sha1:184ce2c6dcdd047d6888fd243b9189c797f58d38</id>
<content type='text'>
* fix: make memcache concurrency-safe and stamp the version at link time

memcache was a crash waiting for traffic. Readers touched tempFS without
holding mx, while a per-entry goroutine deleted from it under the lock: a
concurrent map read and map write, which the runtime treats as a fatal error
that recover cannot catch. The option ships in config.example.json and was the
one cache key SETUP.md never documented, so it read like a free win to enable.

Put every map and field access behind the mutex, and age the whole map from one
janitor instead of a goroutine per cached file, each of which looped forever
holding its entry alive. mx is now a plain Mutex: every operation here mutates
something, and the old code took an RLock to write. Document the option, and
cover it with tests that run the readers, writers and janitor concurrently.

Split the disk/origin fetch out of DownloadAndSendMedia while there, so the
error path returns instead of falling through to write an empty body after the
error page.

Release.Version was hardcoded to 1.3.2, so images tagged v1.3.6 reported 1.3.2
from --help and /api/instance, and --help linked to the wrong release. Take it
from a main.version string the release workflow links in from the git tag.

* fix: reject forged subdomains in the media proxy

DownloadAndSendMedia built its upstream URL by concatenation, pasting the
subdomain segment of the request path straight into the host position. That
segment reaches the handler already percent-decoded, so it can carry "@", "#",
"?" and "/" — the characters that end a host. A request for

    /media/file/x@127.0.0.1:8080%2F/f/x.jpg

built a URL whose host parsed as 127.0.0.1:8080, with images-wixmp-x demoted to
userinfo, letting any caller aim the instance's fetcher at any address it could
reach, including services behind the firewall.

Validate the label against ^[a-zA-Z0-9-]+$ and refuse anything else with a 400.
Rejecting rather than escaping is what closes this: the label is the host, and
url.URL passes a host through verbatim, so building the URL structurally is not
sufficient on its own. DeviantArt's own media URLs use a hex-and-dash label, and
ParseMedia already splits on the first dot, so a legitimate label cannot contain
one.

Build the URL from url.URL fields as well, which escapes the path, and encode
the token argument, which reached the request unescaped.

Reported by CodeQL as go/request-forgery (CWE-918).</content>
</entry>
</feed>
