Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/caddy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ on:
- .github/workflows/caddy.yml
- caddy/Dockerfile
- caddy/Caddyfile
- caddy/cel-go-0.29.patch
- caddy/source-check.sh
pull_request:
paths:
- .github/workflows/caddy.yml
- caddy/Dockerfile
- caddy/Caddyfile
- caddy/cel-go-0.29.patch
- caddy/source-check.sh
workflow_dispatch:
permissions:
Expand Down
6 changes: 6 additions & 0 deletions backend/internal/access-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ const internalAccessList = {
*/
create: async (access, data) => {
access.can("access_lists:manage");
if ((data.items || []).some((item) => !item.password)) {
throw Object.assign(new errs.ValidationError("A password is required for new access-list users"), {
message_i18n: "error.access.password-required",
});
}

const row = await accessListModel.query().insertAndFetch({
name: data.name,
satisfy_any: data.satisfy_any,
Expand Down
1 change: 1 addition & 0 deletions backend/internal/certificate.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const internalCertificate = {
intervalProcessing: false,

initTimer: () => {
if (internalCertificate.interval) return;
logger.info("Certbot Renewal Timer initialized");
internalCertificate.interval = setInterval(
internalCertificate.processExpiringHosts,
Expand Down
1 change: 1 addition & 0 deletions backend/internal/ip_ranges.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const internalIpRanges = {
interval_processing: false,

initTimer: () => {
if (internalIpRanges.interval) return;
logger.info("IP Ranges Renewal Timer initialized");
internalIpRanges.interval = setInterval(internalIpRanges.fetch, internalIpRanges.interval_timeout);
},
Expand Down
1 change: 0 additions & 1 deletion backend/schema/components/redirection-host-object.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
"forward_scheme": {
"type": "string",
"enum": [
"auto",
"$scheme",
"http",
"https"
Expand Down
4 changes: 2 additions & 2 deletions backend/schema/paths/nginx/redirection-hosts/post.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"test.example.com"
],
"forward_domain_name": "example.com",
"forward_scheme": "auto",
"forward_scheme": "$scheme",
"forward_http_code": 301,
"preserve_path": false,
"certificate_id": 0,
Expand Down Expand Up @@ -130,7 +130,7 @@
"enabled": true,
"hsts_enabled": false,
"hsts_subdomains": false,
"forward_scheme": "auto",
"forward_scheme": "$scheme",
"forward_http_code": 301
}
}
Expand Down
1 change: 1 addition & 0 deletions backend/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ const setupAio = async () => {
npmplus_crowdsec_appsec: false,
npmplus_proxy_request_buffering: true,
npmplus_proxy_response_buffering: true,
npmplus_upstream_compression: true,
certificate_id: "new",
ssl_forced: true,
hsts_enabled: true,
Expand Down
11 changes: 11 additions & 0 deletions backend/test/access-list-recovery.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ const seed = async () => {
return list;
};

test("a blank password on create rejects before inserting the list or any credentials", async () => {
await assert.rejects(
internalAccessList.create(access, {
name: "Created",
items: [{ username: "user", password: "" }],
}),
(error) => error.status === 400,
);
assert.equal(await AccessList.query().where("name", "Created").first(), undefined);
});

test("a blank-password rename rejects before changing any access-list fields or credentials", async (t) => {
t.mock.method(internalNginx, "reload", async () => {});
t.mock.method(internalNginx, "test", async () => {});
Expand Down
50 changes: 9 additions & 41 deletions caddy/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,51 +1,19 @@
# syntax=docker/dockerfile:1.27.1@sha256:4edf897a3ffa55b89f906fc8cc78afdb3f1834cc9c7083565e611a8a7d5fe99e
FROM golang:1.26.8-alpine3.23@sha256:33ce311e5eecedee48ec1b84419c1306e9fbd71009f0d5c3f2a6904b579c1ecc AS builder

ARG CADDY_VERSION=v2.11.4
ARG CADDY_VERSION=v2.11.6
ARG XCADDY_VERSION=v0.4.5
ARG X_CRYPTO_VERSION=v0.56.0
ARG X_NET_VERSION=v0.57.0
# v1.83.2 fixes GHSA-2v4p-qf9q-27wj (CVE-2026-84445): xDS servers crash on
# requests missing both :authority and Host headers.
ARG GRPC_VERSION=v1.83.2
ARG CEL_GO_VERSION=v0.30.0
ARG CHI_VERSION=v5.3.0
ARG COMPRESS_VERSION=v1.18.7
# Caddy 2.11.4 pins the OTLP log modules at v0.19.0. That exporter accepted a
# TLS certificate without validating it (CVE-2026-81871, fixed in v0.21.0).
# The log modules carry their own v0.x line, so pin the whole set to the
# aligned otel v1.45.0 release, including the trace exporters (CVE-2026-81870).
ARG OTEL_VERSION=v1.45.0
ARG OTEL_LOG_VERSION=v0.21.0

# Caddy 2.11.4 predates cel-go 0.29's InterpretableV2 API. This two-line
# compatibility patch is taken from upstream commit b2693fb63a30e6d7be0972c3645e9a2c0a500e93.
COPY cel-go-0.29.patch /tmp/cel-go-0.29.patch

# 2.11.6 ships every dependency newer than the CVE pins this build used to
# force with --replace, and carries the InterpretableV2 usage natively, so the
# cel-go patch and the pin set were removed with the 2.11.6 bump. The source
# check in caddy.yml still gates the binary with govulncheck; re-add a single
# --replace here only if that goes red again.
# XCADDY_SKIP_CLEANUP keeps the build environment for that source check.
RUN apk upgrade --no-cache -a && \
apk add --no-cache ca-certificates git patch && \
apk add --no-cache ca-certificates git && \
GOBIN=/usr/local/bin go install "github.com/caddyserver/xcaddy/cmd/xcaddy@$XCADDY_VERSION" && \
go mod download "github.com/caddyserver/caddy/v2@$CADDY_VERSION" && \
cp -a "$(go env GOMODCACHE)/github.com/caddyserver/caddy/v2@$CADDY_VERSION" /tmp/caddy-source && \
chmod -R u+w /tmp/caddy-source && \
patch -d /tmp/caddy-source -p1 < /tmp/cel-go-0.29.patch && \
CGO_ENABLED=0 XCADDY_SETCAP=0 XCADDY_SKIP_CLEANUP=1 xcaddy build "$CADDY_VERSION" --output /usr/bin/caddy \
--with "github.com/caddyserver/caddy/v2@$CADDY_VERSION=/tmp/caddy-source" \
--replace "github.com/google/cel-go=github.com/google/cel-go@$CEL_GO_VERSION" \
--replace "github.com/go-chi/chi/v5=github.com/go-chi/chi/v5@$CHI_VERSION" \
--replace "github.com/klauspost/compress=github.com/klauspost/compress@$COMPRESS_VERSION" \
--replace "golang.org/x/crypto=golang.org/x/crypto@$X_CRYPTO_VERSION" \
--replace "golang.org/x/net=golang.org/x/net@$X_NET_VERSION" \
--replace "google.golang.org/grpc=google.golang.org/grpc@$GRPC_VERSION" \
--replace "go.opentelemetry.io/otel=go.opentelemetry.io/otel@$OTEL_VERSION" \
--replace "go.opentelemetry.io/otel/log=go.opentelemetry.io/otel/log@$OTEL_LOG_VERSION" \
--replace "go.opentelemetry.io/otel/sdk/log=go.opentelemetry.io/otel/sdk/log@$OTEL_LOG_VERSION" \
--replace "go.opentelemetry.io/otel/exporters/otlp/otlptrace=go.opentelemetry.io/otel/exporters/otlp/otlptrace@$OTEL_VERSION" \
--replace "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc=go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc@$OTEL_VERSION" \
--replace "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp=go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp@$OTEL_VERSION" \
--replace "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc=go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc@$OTEL_LOG_VERSION" \
--replace "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp=go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp@$OTEL_LOG_VERSION" \
--replace "go.opentelemetry.io/otel/exporters/stdout/stdoutlog=go.opentelemetry.io/otel/exporters/stdout/stdoutlog@$OTEL_LOG_VERSION" && \
CGO_ENABLED=0 XCADDY_SETCAP=0 XCADDY_SKIP_CLEANUP=1 xcaddy build "$CADDY_VERSION" --output /usr/bin/caddy && \
/usr/bin/caddy version

FROM alpine:3.24.2@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6
Expand Down
8 changes: 0 additions & 8 deletions caddy/cel-go-0.29.patch

This file was deleted.

4 changes: 1 addition & 3 deletions frontend/src/hooks/useAccessList.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ const useSetAccessList = () => {
}));
return () => queryClient.setQueryData(["access-list", values.id], previousObject);
},
onError: (_, __, rollback) => {
if (typeof rollback === "function") rollback();
},
onError: (_, __, rollback) => rollback?.(),
onSuccess: async ({ id }) => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: ["access-list", id] }),
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/hooks/useDeadHost.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@ const useSetDeadHost = () => {
}));
return () => queryClient.setQueryData(["dead-host", values.id], previousObject);
},
onError: (_, __, rollback) => {
if (typeof rollback === "function") rollback();
},
onError: (_, __, rollback) => rollback?.(),
onSuccess: async ({ id }) => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: ["dead-host", id] }),
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/hooks/useProxyHost.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,7 @@ const useSetProxyHost = () => {
}));
return () => queryClient.setQueryData(["proxy-host", values.id], previousObject);
},
onError: (_, __, rollback) => {
if (typeof rollback === "function") rollback();
},
onError: (_, __, rollback) => rollback?.(),
onSuccess: async ({ id }) => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: ["proxy-host", id] }),
Expand Down
6 changes: 2 additions & 4 deletions frontend/src/hooks/useRedirectionHost.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const fetchRedirectionHost = (id) => {
npmplusMtlsCertificateId: 0,
npmplusMtlsVerifyClientOptional: false,
npmplusHttp3Support: false,
forwardScheme: "auto",
forwardScheme: "$scheme",
forwardHttpCode: 301,
enabled: true,
hstsEnabled: false,
Expand Down Expand Up @@ -52,9 +52,7 @@ const useSetRedirectionHost = () => {
}));
return () => queryClient.setQueryData(["redirection-host", values.id], previousObject);
},
onError: (_, __, rollback) => {
if (typeof rollback === "function") rollback();
},
onError: (_, __, rollback) => rollback?.(),
onSuccess: async ({ id }) => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: ["redirection-host", id] }),
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/hooks/useStream.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@ const useSetStream = () => {
}));
return () => queryClient.setQueryData(["stream", values.id], previousObject);
},
onError: (_, __, rollback) => {
if (typeof rollback === "function") rollback();
},
onError: (_, __, rollback) => rollback?.(),
onSuccess: async ({ id }) => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: ["stream", id] }),
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/hooks/useUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ const useSetUser = () => {
}));
return () => queryClient.setQueryData(["user", values.id], previousObject);
},
onError: (_, __, rollback) => {
if (typeof rollback === "function") rollback();
},
onError: (_, __, rollback) => rollback?.(),
onSuccess: async ({ id }) => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: ["user", id] }),
Expand Down
Loading