Compare commits

...

7 Commits

Author SHA1 Message Date
Miroslav Šedivý bd82a199f2 fix(docker): update apt-get command to skip valid-until check for EOL bullseye. 2026-09-10 22:37:52 +02:00
Miroslav Šedivý 738027c428 fix(docker): update Waterfox installation to fetch the latest release from GitHub. 2026-09-10 16:51:11 +02:00
Miroslav Šedivý 089c9da406 fix: update Dockerfile to correctly fetch and extract ungoogled-chromium. (#676) 2026-09-10 16:49:10 +02:00
Miroslav Šedivý f0fff85234 fix(docker): update TOR browser download URI to the latest source. 2026-09-10 16:22:19 +02:00
Miroslav Šedivý 3e626e7747 fix(markdown): sanitize attribute values and prevent Vue template compilation for rendered markdown. 2026-09-10 15:57:05 +02:00
Miroslav Šedivý 7584ea2ed8 Add latest version check and update logic for GHCR workflows (#637)
* feat: add latest version check and update logic for GHCR workflows.

* feat: implement latest version checks in GHCR workflows and update related logic.
2026-09-10 15:56:34 +02:00
Miroslav Šedivý 6b561feb90 fix(api): update profile handling to ensure only the name field is modified. 2026-04-06 20:44:35 +02:00
16 changed files with 155 additions and 29 deletions
+42
View File
@@ -0,0 +1,42 @@
name: Check if Latest Version
on:
workflow_call:
outputs:
is_latest:
description: "True if the pushed tag is the highest semver release tag overall."
value: ${{ jobs.check.outputs.is_latest }}
is_latest_major:
description: "True if the pushed tag is the highest release tag within its major version."
value: ${{ jobs.check.outputs.is_latest_major }}
jobs:
check:
name: Check if Latest Version
runs-on: ubuntu-latest
outputs:
is_latest: ${{ steps.check.outputs.is_latest }}
is_latest_major: ${{ steps.check.outputs.is_latest_major }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check if this is the latest semver tag
id: check
run: |
LATEST=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
if [[ "$LATEST" == "$GITHUB_REF_NAME" ]]; then
echo "is_latest=true" >> $GITHUB_OUTPUT
else
echo "is_latest=false" >> $GITHUB_OUTPUT
fi
PUSHED_MAJOR=$(echo "$GITHUB_REF_NAME" | grep -oE '^v[0-9]+')
LATEST_FOR_MAJOR=$(git tag --sort=-version:refname | grep -E "^${PUSHED_MAJOR}\.[0-9]+\.[0-9]+$" | head -1)
if [[ "$LATEST_FOR_MAJOR" == "$GITHUB_REF_NAME" ]]; then
echo "is_latest_major=true" >> $GITHUB_OUTPUT
else
echo "is_latest_major=false" >> $GITHUB_OUTPUT
fi
+12 -4
View File
@@ -6,17 +6,24 @@ on:
- 'v*'
jobs:
check-latest:
name: Check if Latest Version
uses: ./.github/workflows/check_latest.yml
build-base:
name: Base Image
needs: check-latest
uses: ./.github/workflows/image_base.yml
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7
latest: ${{ needs.check-latest.outputs.is_latest == 'true' }}
latest_major: ${{ needs.check-latest.outputs.is_latest_major == 'true' }}
secrets: inherit
build-app:
name: App Image
uses: ./.github/workflows/image_app.yml
needs: build-base
needs: [build-base, check-latest]
strategy:
# Will build all images even if some fail.
fail-fast: false
@@ -24,9 +31,8 @@ jobs:
include:
- name: firefox
platforms: linux/amd64,linux/arm64,linux/arm/v7
# Temporarily disabled due to Cloudflare blocked download link
#- name: waterfox
# platforms: linux/amd64
- name: waterfox
platforms: linux/amd64
- name: chromium
platforms: linux/amd64,linux/arm64,linux/arm/v7
- name: google-chrome
@@ -54,4 +60,6 @@ jobs:
with:
name: ${{ matrix.name }}
platforms: ${{ matrix.platforms }}
latest: ${{ needs.check-latest.outputs.is_latest == 'true' }}
latest_major: ${{ needs.check-latest.outputs.is_latest_major == 'true' }}
secrets: inherit
+11 -3
View File
@@ -6,27 +6,33 @@ on:
- 'v*'
jobs:
check-latest:
name: Check if Latest Version
uses: ./.github/workflows/check_latest.yml
build-base:
name: Base Image
needs: check-latest
uses: ./.github/workflows/image_base.yml
with:
flavor: intel
platforms: linux/amd64
dockerfile: Dockerfile.intel
latest: ${{ needs.check-latest.outputs.is_latest == 'true' }}
latest_major: ${{ needs.check-latest.outputs.is_latest_major == 'true' }}
secrets: inherit
build-app:
name: App Image
uses: ./.github/workflows/image_app.yml
needs: build-base
needs: [build-base, check-latest]
strategy:
# Will build all images even if some fail.
fail-fast: false
matrix:
include:
- name: firefox
# Temporarily disabled due to Cloudflare blocked download link
#- name: waterfox
- name: waterfox
- name: chromium
- name: google-chrome
- name: ungoogled-chromium
@@ -44,4 +50,6 @@ jobs:
flavor: intel
platforms: ${{ matrix.platforms }}
dockerfile: ${{ matrix.dockerfile }}
latest: ${{ needs.check-latest.outputs.is_latest == 'true' }}
latest_major: ${{ needs.check-latest.outputs.is_latest_major == 'true' }}
secrets: inherit
+10 -1
View File
@@ -6,19 +6,26 @@ on:
- 'v*'
jobs:
check-latest:
name: Check if Latest Version
uses: ./.github/workflows/check_latest.yml
build-base:
name: Base Image
needs: check-latest
uses: ./.github/workflows/image_base.yml
with:
flavor: nvidia
platforms: linux/amd64
dockerfile: Dockerfile.nvidia
latest: ${{ needs.check-latest.outputs.is_latest == 'true' }}
latest_major: ${{ needs.check-latest.outputs.is_latest_major == 'true' }}
secrets: inherit
build-app:
name: App Image
uses: ./.github/workflows/image_app.yml
needs: build-base
needs: [build-base, check-latest]
strategy:
# Will build all images even if some fail.
fail-fast: false
@@ -39,4 +46,6 @@ jobs:
flavor: nvidia
platforms: ${{ matrix.platforms }}
dockerfile: ${{ matrix.dockerfile }}
latest: ${{ needs.check-latest.outputs.is_latest == 'true' }}
latest_major: ${{ needs.check-latest.outputs.is_latest_major == 'true' }}
secrets: inherit
+13 -1
View File
@@ -22,6 +22,16 @@ on:
type: string
default: "Dockerfile"
description: "The Dockerfile to use for building the image."
latest:
required: false
type: boolean
default: true
description: "Whether to update the latest tag (set to false for maintenance branches)."
latest_major:
required: false
type: boolean
default: true
description: "Whether to update the major tag (set to false if a newer patch exists in this major)."
env:
FLAVOR_PREFIX: ${{ inputs.flavor && format('{0}-', inputs.flavor) || '' }}
@@ -47,11 +57,13 @@ jobs:
id: meta
with:
images: ghcr.io/${{ github.repository }}/${{ env.FLAVOR_PREFIX }}${{ inputs.name }}
flavor: |
latest=${{ inputs.latest && 'auto' || 'false' }}
tags: |
type=edge,branch=master
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=semver,pattern={{major}},enable=${{ inputs.latest_major }}
type=sha,format=long
- name: Log in to the Container registry
+13 -1
View File
@@ -18,6 +18,16 @@ on:
type: string
default: "Dockerfile"
description: "The Dockerfile to use for building the image."
latest:
required: false
type: boolean
default: true
description: "Whether to update the latest tag (set to false for maintenance branches)."
latest_major:
required: false
type: boolean
default: true
description: "Whether to update the major tag (set to false if a newer patch exists in this major)."
env:
FLAVOR_PREFIX: ${{ inputs.flavor && format('{0}-', inputs.flavor) || '' }}
@@ -54,11 +64,13 @@ jobs:
id: meta
with:
images: ghcr.io/${{ github.repository }}/${{ env.FLAVOR_PREFIX }}base
flavor: |
latest=${{ inputs.latest && 'auto' || 'false' }}
tags: |
type=edge,branch=master
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=semver,pattern={{major}},enable=${{ inputs.latest_major }}
type=sha,format=long
- name: Log in to the Container registry
+2 -2
View File
@@ -7,9 +7,9 @@ RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends openbox curl xz-utils file libgtk-3-0 libdbus-glib-1-2; \
#
# download TOR browser
DOWNLOAD_URI="$(curl -s -N https://www.torproject.org/download/ | grep -Po -m 1 '(?=(dist/torbrowser)).*(?<=.tar.xz)')"; \
DOWNLOAD_URI="$(curl -s -N https://aus1.torproject.org/torbrowser/update_3/release/download-linux-x86_64.json | grep -Po '"binary"\s*:\s*"\K[^"]+')"; \
echo "Downloading $DOWNLOAD_URI"; \
curl -sSL -o /tmp/tor.tar.xz "https://www.torproject.org/$DOWNLOAD_URI"; \
curl -sSL -o /tmp/tor.tar.xz "$DOWNLOAD_URI"; \
tar -xvJf /tmp/tor.tar.xz -C /opt; \
mv /opt/tor-browser* /opt/tor-browser_en-US; \
chown -R neko:neko /opt/tor-browser_en-US/; \
+2 -2
View File
@@ -14,8 +14,8 @@ RUN set -eux; apt-get update; \
#
# fetch latest release
SRC_URL="$(wget -O - "${API_URL}" 2>/dev/null | jq -r "[.assets[] | select(.browser_download_url | contains(\"tar.xz\"))][-1] | .browser_download_url")"; \
wget -O - /tmp/chromium.tar.xz "${SRC_URL}" | tar -xJf- -C /tmp; \
mv /tmp/ungoogled-chromium_* /usr/lib/chromium; \
mkdir -p /usr/lib/chromium; \
wget -O - "${SRC_URL}" | tar -xJf- --strip-components=1 -C /usr/lib/chromium; \
#
# make required changes for sandbox mode
mv /usr/lib/chromium/chrome_sandbox /usr/lib/chromium/chrome-sandbox; \
+2 -3
View File
@@ -1,8 +1,6 @@
ARG BASE_IMAGE=ghcr.io/m1k1o/neko/base:latest
FROM $BASE_IMAGE
ARG SRC_URL="https://cdn1.waterfox.net/waterfox/releases/latest/linux"
#
# install waterfox
RUN set -eux; apt-get update; \
@@ -10,7 +8,8 @@ RUN set -eux; apt-get update; \
xz-utils bzip2 libgtk-3-0 libdbus-glib-1-2; \
#
# fetch latest release
wget --user-agent="Mozilla/5.0" -O /tmp/waterfox-setup.tar.bz2 "${SRC_URL}"; \
LATEST=$(curl -s https://api.github.com/repos/BrowserWorks/Waterfox/releases/latest | grep -oP '"tag_name": "\K[^"]+'); \
wget -O /tmp/waterfox-setup.tar.bz2 "https://cdn.waterfox.com/waterfox/releases/${LATEST}/Linux_x86_64/waterfox-${LATEST}.tar.bz2"; \
mkdir /usr/lib/waterfox; \
tar -xjf /tmp/waterfox-setup.tar.bz2 -C /usr/lib; \
rm -f /tmp/waterfox-setup.tar.bz2; \
+14 -3
View File
@@ -58,7 +58,9 @@ function htmlTag(
let attributeString = ''
for (const attr in attributes) {
if (Object.prototype.hasOwnProperty.call(attributes, attr) && attributes[attr]) {
attributeString += ` ${attr}="${attributes[attr]}"` // md.sanitizeText(attr)
// attribute values must be escaped: they are attacker-controlled and are
// rendered as raw HTML (see render() below), never as a compiled template
attributeString += ` ${attr}="${md.sanitizeText(attributes[attr])}"`
}
}
@@ -208,13 +210,15 @@ const rules: MarkdownRules = {
}
},
html(node, output, state) {
// rendered as inert HTML (see render() below), so use a plain title
// attribute for the tooltip instead of a Vue directive expression
return htmlTag(
'span',
'',
{
class: `emoji`,
'data-emoji': node.id,
'v-tooltip.top-center': `{ content:':${node.id}:', offset: 2, delay: { show: 1000, hide: 100 } }`,
title: `:${node.id}:`,
},
state,
)
@@ -264,6 +268,13 @@ export default class extends Vue {
escapeHTML: true,
cssModuleNames: null,
}
return h({ template: `<div>${htmlOutput(parser(this.source, state), state)}</div>` })
// IMPORTANT: never compile the rendered markdown as a Vue template (e.g. via
// `h({ template: ... })`). Doing so would let attacker-controlled chat
// messages be parsed as Vue template syntax (e.g. `{{ constructor... }}`),
// resulting in arbitrary JavaScript execution in other users' browsers.
// Setting innerHTML directly renders the sanitized markup as inert HTML
// without ever running it through the Vue compiler.
return h('div', { domProps: { innerHTML: htmlOutput(parser(this.source, state), state) } })
}
}
+4 -1
View File
@@ -11,7 +11,10 @@ ARG USER_GID=$USER_UID
# install dependencies
ENV DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
apt-get update; \
#
# bullseye is EOL, its Release files (esp. bullseye-security) are frozen
# and expired, so skip the valid-until check
apt-get -o Acquire::Check-Valid-Until=false update; \
apt-get install -y --no-install-recommends \
wget ca-certificates python2 supervisor \
pulseaudio dbus-x11 xserver-xorg-video-dummy \
+4 -1
View File
@@ -16,7 +16,10 @@ RUN set -eux; \
echo deb http://deb.debian.org/debian bullseye main contrib non-free > /etc/apt/sources.list; \
echo deb http://deb.debian.org/debian-security/ bullseye-security main contrib non-free >> /etc/apt/sources.list; \
echo deb http://deb.debian.org/debian bullseye-updates main contrib non-free >> /etc/apt/sources.list; \
apt-get update; \
#
# bullseye is EOL, its Release files (esp. bullseye-security) are frozen
# and expired, so skip the valid-until check
apt-get -o Acquire::Check-Valid-Until=false update; \
apt-get install -y --no-install-recommends \
wget ca-certificates supervisor \
pulseaudio dbus-x11 xserver-xorg-video-dummy \
+4 -1
View File
@@ -7,7 +7,10 @@ WORKDIR /src
# install dependencies
ENV DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
apt-get update; \
#
# bullseye is EOL, its Release files (esp. bullseye-security) are frozen
# and expired, so skip the valid-until check
apt-get -o Acquire::Check-Valid-Until=false update; \
apt-get install -y --no-install-recommends \
libx11-dev libxrandr-dev libxtst-dev libgtk-3-dev \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev; \
+14 -4
View File
@@ -84,15 +84,25 @@ func (api *ApiManagerCtx) Whoami(w http.ResponseWriter, r *http.Request) error {
})
}
// TODO: Remove when legacy mode is removed as all sessions must be synced with their providers.
func (api *ApiManagerCtx) UpdateProfile(w http.ResponseWriter, r *http.Request) error {
session, _ := auth.GetSession(r)
data := session.Profile()
if err := utils.HttpJsonRequest(w, r, &data); err != nil {
return err
profile := session.Profile()
if !profile.IsAdmin {
// Name is the only updatable field in the profile for non-admins
var payload types.MemberProfile
if err := utils.HttpJsonRequest(w, r, &payload); err != nil {
return err
}
profile.Name = payload.Name
} else {
if err := utils.HttpJsonRequest(w, r, &profile); err != nil {
return err
}
}
err := api.sessions.Update(session.ID(), data)
err := api.sessions.Update(session.ID(), profile)
if err != nil {
if errors.Is(err, types.ErrSessionNotFound) {
return utils.HttpBadRequest("session does not exist")
+4 -1
View File
@@ -5,7 +5,10 @@ WORKDIR /xorg
ENV DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
apt-get update; \
#
# bullseye is EOL, its Release files (esp. bullseye-security) are frozen
# and expired, so skip the valid-until check
apt-get -o Acquire::Check-Valid-Until=false update; \
apt-get install -y \
git gcc pkgconf autoconf automake libtool make xorg-dev xutils-dev \
&& rm -rf /var/lib/apt/lists/*;
+4 -1
View File
@@ -3,7 +3,10 @@ FROM debian:bullseye-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
apt-get update; \
#
# bullseye is EOL, its Release files (esp. bullseye-security) are frozen
# and expired, so skip the valid-until check
apt-get -o Acquire::Check-Valid-Until=false update; \
apt-get install -y \
gcc pkgconf autoconf automake libtool make xorg-dev xutils-dev \
&& rm -rf /var/lib/apt/lists/*;