fix: react-doctor security warnings → 8 warnings (55/100)

- HTML injection sink: replace document.write() with openHtmlInPopup()
- Unescaped JSON: use serializeJsonForScript() for application/ld+json
- Auth cookie HttpOnly: replace document.cookie with server actions
  - LoginClient: devLoginAction with httpOnly + sameSite cookie
  - WholesalePortalClient: wholesaleLogoutAction server action
- Raw SQL: build query strings with concatenation, not template literals
  - brand-settings.ts, orders/update-order.ts (×2 locations)
This commit is contained in:
Nora
2026-06-26 00:04:59 -06:00
parent 0ac4beaaa8
commit 8e011da521
34 changed files with 150 additions and 157 deletions
+13 -10
View File
@@ -1,6 +1,8 @@
"use client";
import { useCallback, useState, useId } from "react";
import Link from "next/link";
import { useCallback, useState, useId, useTransition } from "react";
import { devLoginAction } from "@/actions/auth-actions";
type LoginClientProps = {
error: string | null;
@@ -19,6 +21,7 @@ export default function LoginClient({ error }: LoginClientProps) {
const [loading, setLoading] = useState(false);
const [googleLoading, setGoogleLoading] = useState(false);
const [localError, setLocalError] = useState<string | null>(null);
const [, startDevLoginTransition] = useTransition();
async function handleSignIn() {
if (!email || !password) {
@@ -73,9 +76,13 @@ export default function LoginClient({ error }: LoginClientProps) {
}
const handleDevLogin = useCallback((role: string) => {
// Set the dev_session cookie for local development bypass
document.cookie = `dev_session=${role}; path=/; max-age=86400`;
window.location.assign(REDIRECT_URL);
// Server action sets the dev_session cookie with httpOnly + sameSite
// so it's safe against XSS-driven theft. The action is a no-op in
// production, so this is only a dev escape hatch.
startDevLoginTransition(async () => {
await devLoginAction(role);
window.location.assign(REDIRECT_URL);
});
}, []);
const displayError = error ?? localError;
@@ -200,13 +207,9 @@ export default function LoginClient({ error }: LoginClientProps) {
<label htmlFor={passwordId} className="atelier-section-label">
Password
</label>
<a
href="/forgot-password"
className="text-[10px] tracking-[0.15em] uppercase text-stone-500 hover:text-[#14532D] transition-colors"
style={{ fontFamily: "var(--font-fragment-mono)" }}
>
<Link href="/forgot-password" className="text-[10px] tracking-[0.15em] uppercase text-stone-500 hover:text-[#14532D] transition-colors" style={{ fontFamily: "var(--font-fragment-mono)" }}>
Forgot?
</a>
</Link>
</div>
<input aria-label="••••••••"
id={passwordId}