fix: keep global settings out of non-admin data exports (GHSA-v3vp-ffww-q7pw)

This commit is contained in:
LukeGus committed 2026-10-07 21:18:08 -05:00
1 parent 8a45d952fd
commit 740d2adaf3
3 files changed
+59 -13

No files matched your search

+10 -13
View File
@@ -81,6 +81,10 @@ import {
} from "./repositories/factory.js";
import { withCurrentSqliteForeignKeysDisabled } from "./repositories/sqlite-foreign-keys.js";
import { applyPluginHostImportSettings } from "./routes/host-plugin-settings.js";
import {
selectExportableSettings,
type SettingData,
} from "../utils/export-settings.js";
import {
keepUsableProtocolCredentials,
listProtocolLogins,
@@ -107,19 +111,9 @@ const requireAdmin = authManager.createAdminMiddleware();
app.use(createCompressionMiddleware());
app.use(createCorsMiddleware());
type SettingData = {
key: string;
value: string;
};
function shouldExportSetting(key: string): boolean {
return !key.startsWith("reset_code_") && !key.startsWith("temp_reset_token_");
}
async function getExportableSettings(): Promise<SettingData[]> {
async function getExportableSettings(isAdmin: boolean): Promise<SettingData[]> {
const settingsRows = await createCurrentSettingsRepository().listAll();
return settingsRows.filter((setting) => shouldExportSetting(setting.key));
return selectExportableSettings(settingsRows, isAdmin);
}
function writeSettingsToExportDatabase(
@@ -1047,7 +1041,10 @@ app.post("/database/export", authenticateJWT, async (req, res) => {
);
}
writeSettingsToExportDatabase(exportDb, await getExportableSettings());
writeSettingsToExportDatabase(
exportDb,
await getExportableSettings(!!user.isAdmin),
);
await writeHostDefaultsToExport(exportDb, userId);
} finally {
exportDb.close();
@@ -0,0 +1,23 @@
import { describe, expect, it } from "vitest";
import { selectExportableSettings } from "../../utils/export-settings.js";
const rows = [
{ key: "allow_registration", value: "true" },
{ key: "audit_log_forward_token", value: "secret" },
{ key: "reset_code_alice", value: "123456" },
{ key: "temp_reset_token_alice", value: "abc" },
{ key: "user_dek_v3_u1", value: "{}" },
];
describe("selectExportableSettings", () => {
it("gives a non-admin export no global settings", () => {
expect(selectExportableSettings(rows, false)).toEqual([]);
});
it("keeps instance config for admins but drops secrets", () => {
expect(selectExportableSettings(rows, true).map((r) => r.key)).toEqual([
"allow_registration",
"user_dek_v3_u1",
]);
});
});
+26
View File
@@ -0,0 +1,26 @@
export type SettingData = {
key: string;
value: string;
};
const SECRET_SETTING_PREFIXES = [
"reset_code_",
"temp_reset_token_",
"audit_log_forward_token",
];
/**
* Global settings are instance config, not user data: only an admin's export
* carries them (import only applies them for admins anyway), and never the
* one-time reset codes or the audit forwarding token.
*/
export function selectExportableSettings(
rows: SettingData[],
isAdmin: boolean,
): SettingData[] {
if (!isAdmin) return [];
return rows.filter(
(row) =>
!SECRET_SETTING_PREFIXES.some((prefix) => row.key.startsWith(prefix)),
);
}