fix(admin): secure proxied model mutations
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { isTrustedAdminMutationRequest } from "../src/lib/admin/auth-policy.ts";
|
||||
|
||||
const adminOrigin = "https://admin.staging.jyotisha.chat";
|
||||
const userOrigin = "https://staging.jyotisha.chat";
|
||||
|
||||
function request(
|
||||
url: string,
|
||||
headers: HeadersInit,
|
||||
): Request {
|
||||
return new Request(url, { method: "POST", headers });
|
||||
}
|
||||
|
||||
test("trusted proxy admin origin accepts the configured host and protocol", () => {
|
||||
const proxied = request("http://admin.staging.jyotisha.chat/api/admin/models", {
|
||||
origin: adminOrigin,
|
||||
host: "admin.staging.jyotisha.chat",
|
||||
"x-forwarded-host": "admin.staging.jyotisha.chat",
|
||||
"x-forwarded-proto": "https",
|
||||
});
|
||||
|
||||
assert.equal(isTrustedAdminMutationRequest(proxied, adminOrigin), true);
|
||||
});
|
||||
|
||||
test("configured admin origin rejects wrong browser origins", () => {
|
||||
const proxied = request("http://admin.staging.jyotisha.chat/api/admin/models", {
|
||||
origin: "https://evil.example",
|
||||
host: "admin.staging.jyotisha.chat",
|
||||
"x-forwarded-host": "admin.staging.jyotisha.chat",
|
||||
"x-forwarded-proto": "https",
|
||||
});
|
||||
|
||||
assert.equal(isTrustedAdminMutationRequest(proxied, adminOrigin), false);
|
||||
});
|
||||
|
||||
test("ordinary staging host cannot call admin mutations", () => {
|
||||
const userHost = request(`${userOrigin}/api/admin/models`, {
|
||||
origin: userOrigin,
|
||||
host: "staging.jyotisha.chat",
|
||||
"x-forwarded-host": "staging.jyotisha.chat",
|
||||
"x-forwarded-proto": "https",
|
||||
});
|
||||
const forgedForwardedHost = request(`${userOrigin}/api/admin/models`, {
|
||||
origin: adminOrigin,
|
||||
host: "staging.jyotisha.chat",
|
||||
"x-forwarded-host": "admin.staging.jyotisha.chat",
|
||||
"x-forwarded-proto": "https",
|
||||
});
|
||||
|
||||
assert.equal(isTrustedAdminMutationRequest(userHost, adminOrigin), false);
|
||||
assert.equal(isTrustedAdminMutationRequest(forgedForwardedHost, adminOrigin), false);
|
||||
});
|
||||
|
||||
test("malformed or ambiguous forwarded origins fail closed", () => {
|
||||
const cases: HeadersInit[] = [
|
||||
{
|
||||
origin: adminOrigin,
|
||||
host: "admin.staging.jyotisha.chat",
|
||||
"x-forwarded-host": "admin.staging.jyotisha.chat, evil.example",
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
{
|
||||
origin: adminOrigin,
|
||||
host: "admin.staging.jyotisha.chat",
|
||||
"x-forwarded-host": "admin.staging.jyotisha.chat",
|
||||
"x-forwarded-proto": "https, http",
|
||||
},
|
||||
{
|
||||
origin: adminOrigin,
|
||||
host: "admin.staging.jyotisha.chat",
|
||||
"x-forwarded-host": "admin.staging.jyotisha.chat/path",
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
{
|
||||
origin: adminOrigin,
|
||||
host: "admin.staging.jyotisha.chat",
|
||||
"x-forwarded-host": "",
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
{
|
||||
origin: adminOrigin,
|
||||
host: "admin.staging.jyotisha.chat",
|
||||
"x-forwarded-host": "admin.staging.jyotisha.chat",
|
||||
},
|
||||
];
|
||||
for (const headers of cases) {
|
||||
assert.equal(
|
||||
isTrustedAdminMutationRequest(
|
||||
request("http://admin.staging.jyotisha.chat/api/admin/models", headers),
|
||||
adminOrigin,
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("direct same-origin requests retain the legacy fallback when no admin origin is configured", () => {
|
||||
const direct = request("https://admin.example/api/admin/models", {
|
||||
origin: "https://admin.example",
|
||||
});
|
||||
|
||||
assert.equal(isTrustedAdminMutationRequest(direct), true);
|
||||
});
|
||||
|
||||
test("invalid configured admin origins fail closed", () => {
|
||||
const proxied = request("http://admin.staging.jyotisha.chat/api/admin/models", {
|
||||
origin: adminOrigin,
|
||||
host: "admin.staging.jyotisha.chat",
|
||||
"x-forwarded-host": "admin.staging.jyotisha.chat",
|
||||
"x-forwarded-proto": "https",
|
||||
});
|
||||
|
||||
for (const configured of [
|
||||
"not-an-origin",
|
||||
`${adminOrigin}/path`,
|
||||
"ftp://admin.staging.jyotisha.chat",
|
||||
"http://admin.staging.jyotisha.chat",
|
||||
]) {
|
||||
assert.equal(isTrustedAdminMutationRequest(proxied, configured), false);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user