Fix security vulnerabilities

This commit is contained in:
2026-10-13 15:52:34 +01:00
parent 74103a19c9
commit eefb2ea5d2
46 changed files with 375 additions and 403 deletions
+8 -2
View File
@@ -1,8 +1,14 @@
type Params = {
key: string;
key?: string;
/**
* Hashed key, as found in cache file names. Used when the original
* key is no longer known (e.g. when trimming from a directory listing).
*/
cache_id?: string;
paradigm?: "html" | "json";
};
export default function grabCacheNames({ key, paradigm }: Params): {
export default function grabCacheNames({ key, cache_id, paradigm, }: Params): {
cache_id: string;
cache_name: string;
cache_meta_name: string;
};
+6 -3
View File
@@ -1,6 +1,9 @@
export default function grabCacheNames({ key, paradigm = "html" }) {
const parsed_key = encodeURIComponent(key);
export default function grabCacheNames({ key, cache_id, paradigm = "html", }) {
// Hashing keeps file names short and fixed-length no matter how long
// the request path or query string is.
const parsed_key = cache_id ||
new Bun.CryptoHasher("sha256").update(key || "").digest("hex");
const cache_name = `${parsed_key}.res.${paradigm}`;
const cache_meta_name = `${parsed_key}.meta.json`;
return { cache_name, cache_meta_name };
return { cache_id: parsed_key, cache_name, cache_meta_name };
}
+2 -8
View File
@@ -9,14 +9,8 @@ export default async function trimAllCache() {
const cached_item = cached_items[i];
if (!cached_item.endsWith(`.meta.json`))
continue;
const cache_key = decodeURIComponent(cached_item.replace(/\.meta\.json/, ""));
const trim_key = await trimCacheKey({
key: cache_key,
});
if (trim_key.success) {
cached_items.splice(i, 1);
i--;
}
const cache_id = cached_item.replace(/\.meta\.json$/, "");
await trimCacheKey({ cache_id });
}
}
catch (error) {
+3 -2
View File
@@ -1,6 +1,7 @@
import type { APIResponseObject } from "../../types";
type Params = {
key: string;
key?: string;
cache_id?: string;
};
export default function trimCacheKey({ key, }: Params): Promise<APIResponseObject>;
export default function trimCacheKey({ key, cache_id, }: Params): Promise<APIResponseObject>;
export {};
+11 -8
View File
@@ -1,21 +1,24 @@
import { readFileSync, unlinkSync } from "fs";
import { readFileSync, rmSync } from "fs";
import grabDirNames from "../../utils/grab-dir-names";
import grabCacheNames from "./grab-cache-names";
import path from "path";
import { AppData } from "../../data/app-data";
export default async function trimCacheKey({ key, }) {
export default async function trimCacheKey({ key, cache_id, }) {
try {
const { BUNEXT_CACHE_DIR } = grabDirNames();
const { cache_name, cache_meta_name } = grabCacheNames({
key,
});
const { cache_meta_name } = grabCacheNames({ key, cache_id });
const config = global.BUNEXT_CONFIG;
const default_expiry_time_seconds = config.default_cache_expiry ||
AppData["DefaultCacheExpiryTimeSeconds"];
const default_expiry_time_milliseconds = default_expiry_time_seconds * 1000;
const cache_content_path = path.join(BUNEXT_CACHE_DIR, cache_name);
const cache_meta_path = path.join(BUNEXT_CACHE_DIR, cache_meta_name);
const cache_meta = JSON.parse(readFileSync(cache_meta_path, "utf-8"));
const { cache_name } = grabCacheNames({
key,
cache_id,
paradigm: cache_meta.paradigm,
});
const cache_content_path = path.join(BUNEXT_CACHE_DIR, cache_name);
const expiry_milliseconds = cache_meta.expiry_seconds
? cache_meta.expiry_seconds * 1000
: default_expiry_time_milliseconds;
@@ -25,8 +28,8 @@ export default async function trimCacheKey({ key, }) {
msg: `Cache has not expired yet`,
};
}
unlinkSync(cache_content_path);
unlinkSync(cache_meta_path);
rmSync(cache_content_path, { force: true });
rmSync(cache_meta_path, { force: true });
return {
success: true,
};
+12 -2
View File
@@ -1,7 +1,8 @@
import { existsSync, writeFileSync } from "fs";
import { existsSync, readdirSync, writeFileSync } from "fs";
import grabDirNames from "../../utils/grab-dir-names";
import grabCacheNames from "./grab-cache-names";
import path from "path";
import { AppData } from "../../data/app-data";
export default async function writeCache({ key, value, paradigm = "html", expiry_seconds, }) {
try {
const { BUNEXT_CACHE_DIR } = grabDirNames();
@@ -16,7 +17,16 @@ export default async function writeCache({ key, value, paradigm = "html", expiry
msg: `Cache entry already exists`,
};
}
writeFileSync(path.join(target_path), value);
const max_entries = global.BUNEXT_CONFIG?.max_cache_entries ||
AppData["DefaultMaxCacheEntries"];
const entries_count = readdirSync(BUNEXT_CACHE_DIR).filter((f) => f.endsWith(".meta.json")).length;
if (entries_count >= max_entries) {
return {
success: false,
msg: `Cache entries limit (${max_entries}) reached`,
};
}
writeFileSync(target_path, value);
const cache_file_meta = {
date_created: Date.now(),
paradigm,