Dashboard reads COOLIFY_READ_TOKEN, and the name is the point

The owner issued a read-only Coolify token, which is the right shape: this
dashboard only ever performs GETs. Renaming the variable from COOLIFY_TOKEN to
COOLIFY_READ_TOKEN means the requirement is encoded where someone setting it will
see it, rather than living only in a doc.

A write-capable token here would mean a PocketBase vulnerability could deploy or
stop every app on the PaaS. coolify.sh keeps using the full token for deploys;
having both in secrets/amber.env is the separation, not redundancy.

Verified the read token against both endpoints the dashboard needs (applications
list, container logs) before wiring it, and validated the renamed variable on a
clean local boot: 7 services enumerated, both panels configured, no errors.
This commit is contained in:
Claude 2026-08-05 21:28:28 +02:00
parent 7a4f0d91f3
commit cd5d7138d9

View file

@ -28,6 +28,11 @@
// **The Coolify token never reaches the browser.** That is the entire reason this // **The Coolify token never reaches the browser.** That is the entire reason this
// endpoint exists rather than the page calling Coolify directly. // endpoint exists rather than the page calling Coolify directly.
// //
// The variable is COOLIFY_**READ**_TOKEN, and the name is the point: this dashboard
// only ever performs GETs, so it must never be handed the write-capable token that
// `coolify.sh` uses for deploys. A full token here would mean a PocketBase
// vulnerability could deploy or stop every app on the PaaS.
//
// NOTE: PocketBase runs each routerAdd handler in its own isolated JSVM, so // NOTE: PocketBase runs each routerAdd handler in its own isolated JSVM, so
// anything a handler needs must be declared INSIDE it — file-scope helpers throw // anything a handler needs must be declared INSIDE it — file-scope helpers throw
// ReferenceError at request time. Nothing is hoisted here; keep it that way. // ReferenceError at request time. Nothing is hoisted here; keep it that way.
@ -134,11 +139,11 @@ routerAdd("GET", "/api/status", (e) => {
// above still render on a box where these env vars were never set. // above still render on a box where these env vars were never set.
const coolify = safe("coolify", () => { const coolify = safe("coolify", () => {
const base = env("COOLIFY_URL") const base = env("COOLIFY_URL")
const token = env("COOLIFY_TOKEN") const token = env("COOLIFY_READ_TOKEN")
if (!base || !token) { if (!base || !token) {
return { return {
configured: false, configured: false,
why: "COOLIFY_URL / COOLIFY_TOKEN are not set on this container — add them " + why: "COOLIFY_URL / COOLIFY_READ_TOKEN are not set on this container — add them " +
"in Coolify → amber-backend → Environment Variables and redeploy.", "in Coolify → amber-backend → Environment Variables and redeploy.",
} }
} }
@ -211,9 +216,9 @@ routerAdd("GET", "/api/status/logs", (e) => {
if (!(lines > 0) || lines > 1000) lines = 200 if (!(lines > 0) || lines > 1000) lines = 200
const base = $os.getenv("COOLIFY_URL") const base = $os.getenv("COOLIFY_URL")
const token = $os.getenv("COOLIFY_TOKEN") const token = $os.getenv("COOLIFY_READ_TOKEN")
if (!base || !token) { if (!base || !token) {
return e.json(503, { error: "COOLIFY_URL / COOLIFY_TOKEN not set on this container" }) return e.json(503, { error: "COOLIFY_URL / COOLIFY_READ_TOKEN not set on this container" })
} }
try { try {
const res = $http.send({ const res = $http.send({