Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
53c927f
✨ feat: Audit log UI for SystemGrants changes
dustinhealy May 11, 2026
2a7ec74
🔒 fix: Harden audit log feature — CSV injection, a11y, click-ui, serv…
dustinhealy May 11, 2026
49226bd
✨ feat: Audit log — pagination, faceted filters, server CSV, side dra…
dustinhealy May 11, 2026
b6c1b73
✨ feat: Audit log infinite pagination, faceted filters, structured se…
dustinhealy May 11, 2026
2714f0d
🧹 chore: Audit log polish — UX, offset pagination, Radix Dialog drawe…
dustinhealy May 12, 2026
2c74905
🔒 fix: CSP report-only fallback to unblock SSR hydration
dustinhealy May 14, 2026
1582498
🛠️ fix: Stabilize `localize` reference across renders
dustinhealy May 14, 2026
26b90c9
🔒 fix: Close CSV formula-injection prefix gaps
dustinhealy May 14, 2026
5b2d1ae
⚡ perf: De-duplicate effective-capabilities fetch per audit-log call
dustinhealy May 14, 2026
ea79bd6
🧹 chore: Extract `useDebouncedFilter` hook
dustinhealy May 14, 2026
403b263
🛠️ fix: Audit log deep-links, exports, clipboard, and TZ correctness
dustinhealy May 14, 2026
3768391
⚡ perf: Drop BFF audit-log capability guard
dustinhealy May 14, 2026
eb7f068
🛡️ feat: Gate audit-log tab on `READ_AUDIT_LOG` capability
dustinhealy May 14, 2026
969f7f8
🛠️ chore: Drop READ_AUDIT_LOG shim, use SystemCapabilities directly
dustinhealy May 14, 2026
e8f02d6
📦 chore: Bump @librechat/data-schemas to ^0.0.52
dustinhealy May 14, 2026
5bfc287
🎨 chore: sort imports in src/server/auth.ts after rebase
dustinhealy Jun 2, 2026
ed1af71
📦 chore: bump librechat-data-provider to ^0.8.503
dustinhealy Jun 2, 2026
20011ca
🧹 chore: drop client-side audit-log CSV path
dustinhealy Jun 2, 2026
32558cf
♿ fix: announce filter match total to screen readers, not page slice
dustinhealy Jun 2, 2026
5c43a4a
♿ fix: gate audit-log a11y announcements + surface export failures
dustinhealy Jun 2, 2026
15fd309
♿ fix: re-apply DatePickerCell tabIndex patch after Clear remount
dustinhealy Jun 2, 2026
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
8 changes: 4 additions & 4 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
},
"dependencies": {
"@clickhouse/click-ui": "0.2.0-rc.4",
"@librechat/data-schemas": "^0.0.51",
"@librechat/data-schemas": "^0.0.52",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/react-devtools": "0.10.0",
"@tanstack/react-query": "5.95.2",
Expand All @@ -41,7 +41,7 @@
"i18next-browser-languagedetector": "^8.2.1",
"input-otp": "^1.4.2",
"js-yaml": "^4.1.1",
"librechat-data-provider": "^0.8.502",
"librechat-data-provider": "^0.8.503",
"lucide-react": "^0.545.0",
"prom-client": "^15.1.3",
"react": "^19.2.0",
Expand Down
48 changes: 43 additions & 5 deletions server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,43 @@ function getCacheHeaders(filePath: string): Record<string, string> {
return {};
}

// 'unsafe-inline' in style-src is required because Tailwind 4 + click-ui inject inline styles at runtime.
// TanStack Start's SSR injects an inline `<script type="module">import("/_build/...")</script>` to
// boot the client. Without a nonce or 'unsafe-inline' for script-src, browsers will block hydration.
// Threading a per-request nonce through TanStack Start's manifest is non-trivial; until that wiring
// lands we ship the policy as report-only so it surfaces violations in dev tooling without breaking
// hydration in prod. Set ADMIN_PANEL_CSP_ENFORCE=true to switch back to enforcement (only safe once
// the nonce path is in place).
const CSP_VALUE = [
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob:",
"font-src 'self' data:",
"connect-src 'self'",
"object-src 'none'",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
].join('; ');

const CSP_ENFORCE = process.env.ADMIN_PANEL_CSP_ENFORCE === 'true';
const CSP_HEADER_NAME = CSP_ENFORCE
? 'Content-Security-Policy'
: 'Content-Security-Policy-Report-Only';

function applySecurityHeaders(headers: Headers): void {
const contentType = headers.get('Content-Type') ?? '';
if (!contentType.toLowerCase().startsWith('text/html')) return;
headers.set(CSP_HEADER_NAME, CSP_VALUE);
headers.set('X-Content-Type-Options', 'nosniff');
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
headers.set('X-Frame-Options', 'DENY');
if (process.env.NODE_ENV === 'production') {
headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
}

type Handler = { default: { fetch: (req: Request) => Promise<Response> } };

const { default: handler } = (await import(SERVER_ENTRY.href)) as Handler;
Expand All @@ -65,11 +102,11 @@ async function buildStaticRoutes(): Promise<Record<string, (req: Request) => Pro
const cache = getCacheHeaders(path);
const routePath = `/${path}`;
routes[routePath] = (req) =>
withHttpMetrics(
req,
routePath,
() => new Response(file, { headers: { 'Content-Type': file.type, ...cache } }),
);
withHttpMetrics(req, routePath, () => {
const res = new Response(file, { headers: { 'Content-Type': file.type, ...cache } });
applySecurityHeaders(res.headers);
return res;
});
}
return routes;
}
Expand All @@ -86,6 +123,7 @@ const server = Bun.serve({
for (const [k, v] of Object.entries(NO_CACHE)) {
patched.headers.set(k, v);
}
applySecurityHeaders(patched.headers);
return patched;
},
},
Expand Down
Loading
Loading