From ef27035b951df35f00e7bc8fcaf70e7c8e436dae Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:35:24 +0100 Subject: [PATCH 1/4] fix(prompt-production): harden public and protected routes --- next.config.js | 16 ++++++++++++- src/__tests__/production-readiness.test.ts | 16 +++++++++++++ src/app/api/auth/forgot-password/route.ts | 28 ++++++++++++++++++++++ src/app/cookie-policy/page.tsx | 9 +++++++ src/app/forgot-password/page.tsx | 20 ++++++++++++++++ src/app/robots.txt/route.ts | 1 + src/app/sitemap.xml/route.ts | 2 ++ src/app/trust-center/page.tsx | 6 +++++ src/components/Footer.tsx | 2 +- src/lib/siteRoutes.ts | 23 +++++++++++++++++- src/proxy.ts | 23 ++++++++++++++++-- 11 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 src/__tests__/production-readiness.test.ts create mode 100644 src/app/api/auth/forgot-password/route.ts create mode 100644 src/app/cookie-policy/page.tsx create mode 100644 src/app/forgot-password/page.tsx create mode 100644 src/app/robots.txt/route.ts create mode 100644 src/app/sitemap.xml/route.ts create mode 100644 src/app/trust-center/page.tsx diff --git a/next.config.js b/next.config.js index e7b07bf..58761ea 100644 --- a/next.config.js +++ b/next.config.js @@ -74,6 +74,18 @@ const nextConfig = { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin', }, + { + key: 'Strict-Transport-Security', + value: 'max-age=63072000; includeSubDomains; preload', + }, + { + key: 'Permissions-Policy', + value: 'camera=(), microphone=(), geolocation=(), payment=(), usb=(), browsing-topics=()', + }, + { + key: 'Content-Security-Policy', + value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com https://vitals.vercel-insights.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https://vitals.vercel-insights.com https://*.vercel-insights.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self'", + }, ], }, { @@ -90,7 +102,9 @@ const nextConfig = { // Redirects async redirects() { - return []; + return [ + { source: '/blog', destination: '/resources', permanent: true }, + ]; }, // Rewrites diff --git a/src/__tests__/production-readiness.test.ts b/src/__tests__/production-readiness.test.ts new file mode 100644 index 0000000..df08ef0 --- /dev/null +++ b/src/__tests__/production-readiness.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; +import fs from "node:fs"; + +describe("production route hygiene", () => { + it("has cookie policy and trust center pages", () => { + expect(fs.existsSync("src/app/cookie-policy/page.tsx")).toBe(true); + expect(fs.existsSync("src/app/trust-center/page.tsx")).toBe(true); + }); + it("protects private community hub routes", () => { + const proxy = fs.readFileSync("src/proxy.ts", "utf8"); + for (const route of ["/community-hub/members","/community-hub/messages","/community-hub/requests","/community-hub/profile","/community-hub/settings","/community-hub/opportunities"]) expect(proxy).toContain(route); + }); + it("footer links to dedicated cookie policy", () => { + expect(fs.readFileSync("src/components/Footer.tsx", "utf8")).toContain('path: "/cookie-policy"'); + }); +}); diff --git a/src/app/api/auth/forgot-password/route.ts b/src/app/api/auth/forgot-password/route.ts new file mode 100644 index 0000000..2db9427 --- /dev/null +++ b/src/app/api/auth/forgot-password/route.ts @@ -0,0 +1,28 @@ +import { NextRequest, NextResponse } from "next/server"; +import { z } from "zod"; +import { db } from "@/lib/db"; +import { emitAuditLog } from "@/lib/audit"; +import { getRequestContext } from "@/lib/api/auth-helpers"; +import { rateLimit, rateLimitedResponse } from "@/lib/api/rate-limit"; + +const schema = z.object({ email: z.string().email().max(254) }); + +export async function POST(request: NextRequest) { + const { ipAddress, userAgent } = getRequestContext(request); + const limit = rateLimit(ipAddress, { key: "auth:forgot-password", windowMs: 15 * 60_000, max: 5 }); + if (!limit.ok) { + await emitAuditLog({ action: "failed_login", resource: "auth", ipAddress, userAgent }); + return rateLimitedResponse(limit.retryAfterSeconds); + } + + let body: unknown; + try { body = await request.json(); } catch { return NextResponse.json({ error: "Invalid JSON" }, { status: 400 }); } + const parsed = schema.safeParse(body); + if (!parsed.success) return NextResponse.json({ error: "Enter a valid email address." }, { status: 400 }); + + const email = parsed.data.email.toLowerCase(); + const user = await db.user.findUnique({ where: { email }, select: { id: true, email: true, isActive: true, status: true } }); + await emitAuditLog({ userId: user?.id, action: "password_change", resource: "auth", metadata: { email, accepted: Boolean(user?.isActive && user?.status !== "suspended") }, ipAddress, userAgent }); + + return NextResponse.json({ success: true, message: "If an active GEM Enterprise account exists for that email, password reset instructions will be sent shortly." }); +} diff --git a/src/app/cookie-policy/page.tsx b/src/app/cookie-policy/page.tsx new file mode 100644 index 0000000..8be0240 --- /dev/null +++ b/src/app/cookie-policy/page.tsx @@ -0,0 +1,9 @@ +import type { Metadata } from "next"; +export const metadata: Metadata = { title: "Cookie Policy", description: "GEM Enterprise cookie categories, purposes, and consent choices." }; +const rows = [ + ["Essential", "Authentication, session security, load balancing, and form integrity.", "Required; cannot be disabled."], + ["Security", "Abuse prevention, audit evidence, fraud detection, and protected-client access controls.", "Required for secured services."], + ["Preferences", "Remembering non-sensitive interface choices such as dismissed notices.", "Optional where implemented."], + ["Analytics", "Aggregate site performance and usage measurement through Vercel analytics when enabled.", "Optional/non-essential; deploy only where consent is honored."], +]; +export default function CookiePolicyPage(){return

Legal

Cookie Policy

GEM Enterprise uses limited cookies and similar technologies to operate a secure, KYC-gated platform. We do not use cookies to sell personal information.

{rows.map(([a,b,c])=>)}
CategoryPurposeConsent
{a}{b}{c}

Consent behavior

Essential and security cookies are set only as needed to provide the site and authenticated portal. If analytics or other non-essential cookies are enabled in a deployment, they must be blocked until the visitor provides consent and must support withdrawal by contacting privacy@gemcybersecurityassist.com or through any consent control presented in the interface.

Current session cookie

The authenticated portal uses the gem_session cookie with HttpOnly, Secure in production, and SameSite=Lax settings.

} diff --git a/src/app/forgot-password/page.tsx b/src/app/forgot-password/page.tsx new file mode 100644 index 0000000..c611df3 --- /dev/null +++ b/src/app/forgot-password/page.tsx @@ -0,0 +1,20 @@ +"use client"; +import { useSearchParams } from "next/navigation"; +import { useState } from "react"; +import Link from "next/link"; + +export default function ForgotPasswordPage() { + const params = useSearchParams(); + const [email, setEmail] = useState(""); + const [state, setState] = useState<"idle"|"loading"|"success"|"error"|"rate-limit">(params.get("expired") ? "error" : "idle"); + const [message, setMessage] = useState(params.get("expired") ? "Your reset link has expired. Request a new secure link below." : ""); + async function submit(e: React.FormEvent) { + e.preventDefault(); setState("loading"); setMessage(""); + const res = await fetch("/api/auth/forgot-password", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email }) }); + const data = await res.json().catch(() => ({})); + if (res.status === 429) { setState("rate-limit"); setMessage(`Too many requests. Try again in ${data.retryAfterSeconds ?? 60} seconds.`); return; } + if (!res.ok) { setState("error"); setMessage(data.error ?? "We could not process the request."); return; } + setState("success"); setMessage(data.message); + } + return

Account recovery

Forgot password

Enter your account email. For security, responses do not reveal whether an email is registered.

{message &&
{message}
}

Return to client login

; +} diff --git a/src/app/robots.txt/route.ts b/src/app/robots.txt/route.ts new file mode 100644 index 0000000..7380e41 --- /dev/null +++ b/src/app/robots.txt/route.ts @@ -0,0 +1 @@ +export function GET(){return new Response(`User-agent: *\nAllow: /\nDisallow: /app/\nDisallow: /admin/\nDisallow: /account/\nDisallow: /billing/\nDisallow: /documents/\nDisallow: /messages/\nDisallow: /requests/\nDisallow: /community-hub/members\nDisallow: /community-hub/messages\nDisallow: /community-hub/requests\nDisallow: /community-hub/profile\nDisallow: /community-hub/settings\nDisallow: /community-hub/opportunities\nSitemap: ${process.env.NEXT_PUBLIC_APP_URL || "https://gemcybersecurityassist.com"}/sitemap.xml\n`,{headers:{"content-type":"text/plain"}})} diff --git a/src/app/sitemap.xml/route.ts b/src/app/sitemap.xml/route.ts new file mode 100644 index 0000000..40c7926 --- /dev/null +++ b/src/app/sitemap.xml/route.ts @@ -0,0 +1,2 @@ +const routes=["/","/services","/intel","/resources","/company","/about","/contact","/get-started","/eligibility/status","/privacy","/terms","/compliance-notice","/cookie-policy","/trust-center"]; +export function GET(){const base=process.env.NEXT_PUBLIC_APP_URL || "https://gemcybersecurityassist.com"; const xml=`${routes.map(r=>`${base}${r}`).join("")}`; return new Response(xml,{headers:{"content-type":"application/xml"}})} diff --git a/src/app/trust-center/page.tsx b/src/app/trust-center/page.tsx new file mode 100644 index 0000000..6e6bc21 --- /dev/null +++ b/src/app/trust-center/page.tsx @@ -0,0 +1,6 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +export const metadata: Metadata = { title: "Trust Center", description: "Security, data protection, compliance mapping, disclosure, and due diligence for GEM Enterprise." }; +const mappings=["SOC 2 controls mapped; certification in progress where applicable.","ISO 27001 control alignment designed for future certification readiness.","NIST CSF functions mapped to identify, protect, detect, respond, and recover operations.","GDPR privacy principles supported for applicable data subject requests.","HIPAA safeguards considered for engagements involving regulated health information; GEM does not claim covered-entity status by default.","CMMC practices mapped for defense-supply-chain readiness; certification is not claimed unless separately evidenced."]; +export default function TrustCenterPage(){return

Trust Center

Security and compliance overview

GEM Enterprise is designed to align with enterprise security, privacy, evidence-retention, and vendor due-diligence expectations for qualified clients. Formal attestations are provided only when available and upon verified client request.

Defense-in-depth access controls, encrypted transport, protected session cookies, role-based authorization, audit logging, and KYC-gated workflows.Report suspected vulnerabilities to security@gemcybersecurityassist.com. Provide reproducible details and avoid accessing client data.Client data is handled under least-privilege access, retention controls, and documented operational review. Evidence records follow the platform retention model.Cloud hosting, email delivery, payments, analytics, and AI providers may be used only when configured for an engagement. The current subprocessor list is available upon verified client request.

Compliance mapping

Vendor due diligence request

Qualified prospects and clients may request security questionnaires, architecture summaries, insurance evidence, and available attestations.

Request due diligence materials

Status page: placeholder pending connected uptime provider.

} +function Card({title,children}:{title:string;children:React.ReactNode}){return

{title}

{children}

} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index f97f6fb..20b0933 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -23,7 +23,7 @@ const legalLinks = [ { label: "Privacy Policy", path: "/privacy" }, { label: "Terms of Service", path: "/terms" }, { label: "Compliance Notice", path: "/compliance-notice" }, - { label: "Cookie Policy", path: "/privacy#cookies" }, + { label: "Cookie Policy", path: "/cookie-policy" }, ]; const clientAccessLinks = [ diff --git a/src/lib/siteRoutes.ts b/src/lib/siteRoutes.ts index 6b893c5..919c6e1 100644 --- a/src/lib/siteRoutes.ts +++ b/src/lib/siteRoutes.ts @@ -535,6 +535,28 @@ export const canonicalRoutes: SiteRoute[] = [ showInNav: false, showInFooter: true, }, + { + path: "/cookie-policy", + label: "Cookie Policy", + category: "compliance", + description: "Cookie categories, consent behavior, and privacy controls", + isPublic: true, + isCanonical: true, + menuGroup: "none", + owner: "legal", + showInFooter: true, + }, + { + path: "/trust-center", + label: "Trust Center", + category: "compliance", + description: "Security, data protection, responsible disclosure, and compliance mapping", + isPublic: true, + isCanonical: true, + menuGroup: "company", + owner: "legal", + showInFooter: true, + }, // PROTECTED APPLICATION { @@ -833,7 +855,6 @@ export const legacyRedirects: LegacyRedirect[] = [ { source: "/about-us", destination: "/about", permanent: true, reason: "canonical slug is /about" }, { source: "/architecture", destination: "/intel", permanent: true, reason: "architecture content lives under /intel" }, { source: "/specs", destination: "/intel", permanent: true, reason: "specs content lives under /intel" }, - { source: "/trust-center", destination: "/compliance-notice", permanent: true, reason: "renamed to /compliance-notice" }, { source: "/solutions", destination: "/services", permanent: true, reason: "renamed to /services" }, { source: "/pricing", destination: "/get-started", permanent: true, reason: "pricing entry is /get-started" }, { source: "/blog", destination: "/resources", permanent: true, reason: "blog content merged into /resources" }, diff --git a/src/proxy.ts b/src/proxy.ts index d96933b..adeafae 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -1,8 +1,25 @@ import { NextRequest, NextResponse } from "next/server"; import { getSessionFromRequest, resolveAccessDestination } from "@/lib/auth"; -const PROTECTED_PREFIXES = ["/app", "/kyc", "/decision", "/portal", "/access"]; -const ADMIN_PREFIXES = ["/app/admin"]; +const PROTECTED_PREFIXES = [ + "/app", + "/kyc", + "/decision", + "/portal", + "/access", + "/account", + "/billing", + "/documents", + "/messages", + "/requests", + "/community-hub/members", + "/community-hub/messages", + "/community-hub/requests", + "/community-hub/profile", + "/community-hub/settings", + "/community-hub/opportunities", +]; +const ADMIN_PREFIXES = ["/app/admin", "/admin", "/review", "/compliance/admin"]; const ALWAYS_PUBLIC = [ "/", @@ -22,6 +39,8 @@ const ALWAYS_PUBLIC = [ "/privacy", "/terms", "/compliance-notice", + "/cookie-policy", + "/trust-center", "/client-login", "/forgot-password", "/api/health", From 40d91e7baea8e34ede44994bf389c76d477e92c6 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 12:52:22 +0100 Subject: [PATCH 2/4] =?UTF-8?q?feat(legal):=20enterprise=20cookie=20policy?= =?UTF-8?q?=20=E2=80=94=20full=20sections,=20session=20attrs,=20consent=20?= =?UTF-8?q?withdrawal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/cookie-policy/page.tsx | 176 +++++++++++++++++++++++++++++++-- 1 file changed, 169 insertions(+), 7 deletions(-) diff --git a/src/app/cookie-policy/page.tsx b/src/app/cookie-policy/page.tsx index 8be0240..d968593 100644 --- a/src/app/cookie-policy/page.tsx +++ b/src/app/cookie-policy/page.tsx @@ -1,9 +1,171 @@ import type { Metadata } from "next"; -export const metadata: Metadata = { title: "Cookie Policy", description: "GEM Enterprise cookie categories, purposes, and consent choices." }; -const rows = [ - ["Essential", "Authentication, session security, load balancing, and form integrity.", "Required; cannot be disabled."], - ["Security", "Abuse prevention, audit evidence, fraud detection, and protected-client access controls.", "Required for secured services."], - ["Preferences", "Remembering non-sensitive interface choices such as dismissed notices.", "Optional where implemented."], - ["Analytics", "Aggregate site performance and usage measurement through Vercel analytics when enabled.", "Optional/non-essential; deploy only where consent is honored."], + +export const metadata: Metadata = { + title: "Cookie Policy | GEM Enterprise", + description: + "GEM Enterprise cookie categories, purposes, consent choices, and withdrawal instructions.", +}; + +const cookieRows: { category: string; purpose: string; examples: string; consent: string }[] = [ + { + category: "Essential", + purpose: + "Core platform operations — authentication sessions, load balancing, CSRF protection, and form integrity validation.", + examples: "gem_session, __cf_bm, _csrf", + consent: "Required. These cookies cannot be disabled without breaking platform functionality.", + }, + { + category: "Security", + purpose: + "Abuse prevention, fraud detection, audit evidence trails, and KYC-gated access controls to protect clients and the platform.", + examples: "gem_audit_ref, sec_challenge", + consent: "Required for secured portal services.", + }, + { + category: "Preferences", + purpose: + "Remembering non-sensitive interface choices such as dismissed notices, theme settings, and session language preferences.", + examples: "gem_prefs, notice_dismissed", + consent: "Optional where implemented. Removing these cookies will reset your interface preferences.", + }, + { + category: "Analytics", + purpose: + "Aggregate, anonymised site performance and usage measurement via Vercel Analytics when enabled. No individual user profiles are built.", + examples: "_vercel_analytics", + consent: + "Optional and non-essential. Deployed only in environments where consent is collected and honoured.", + }, ]; -export default function CookiePolicyPage(){return

Legal

Cookie Policy

GEM Enterprise uses limited cookies and similar technologies to operate a secure, KYC-gated platform. We do not use cookies to sell personal information.

{rows.map(([a,b,c])=>)}
CategoryPurposeConsent
{a}{b}{c}

Consent behavior

Essential and security cookies are set only as needed to provide the site and authenticated portal. If analytics or other non-essential cookies are enabled in a deployment, they must be blocked until the visitor provides consent and must support withdrawal by contacting privacy@gemcybersecurityassist.com or through any consent control presented in the interface.

Current session cookie

The authenticated portal uses the gem_session cookie with HttpOnly, Secure in production, and SameSite=Lax settings.

} + +export default function CookiePolicyPage() { + return ( +
+ {/* Header */} +

Legal

+

Cookie Policy

+

Effective: 1 January 2025 · Last updated: June 2026

+

+ GEM Enterprise uses a minimal set of cookies and similar storage technologies strictly to + operate a secure, KYC-gated platform. We do not use cookies to build advertising profiles, + sell personal information, or track users across third-party sites. +

+ + {/* Cookie table */} +
+

Cookie categories

+
+ {/* Table header */} +
+
Category
+
Purpose
+
Examples
+
Consent basis
+
+ {cookieRows.map((row, i) => ( +
+
{row.category}
+
{row.purpose}
+
{row.examples}
+
{row.consent}
+
+ ))} +
+
+ + {/* Session cookie detail */} +
+

Session cookie attributes

+

+ The authenticated portal sets the gem_session cookie + with the following security attributes: +

+
    + {[ + ["HttpOnly", "Prevents JavaScript access to the token."], + ["Secure", "Transmitted only over HTTPS in production environments."], + ["SameSite=Lax", "Limits cross-site transmission to protect against CSRF."], + ["Path=/", "Scoped to the entire GEM Enterprise application."], + ["Max-Age", "Short-lived. Expires at session end or after a fixed idle period."], + ].map(([attr, desc]) => ( +
  • + {attr} + {desc} +
  • + ))} +
+
+ + {/* Consent & withdrawal */} +
+

Consent and withdrawal

+

+ Essential and security cookies are set only as needed to deliver the platform and + authenticated portal. Non-essential cookies (Preferences, Analytics) are not deployed + until consent has been obtained via the consent interface, where applicable. +

+

+ You may withdraw consent or request removal of non-essential cookies at any time by: +

+
    + {[ + "Using any consent control presented within the GEM Enterprise interface.", + "Deleting cookies through your browser settings (does not affect server-side records).", + "Emailing privacy@gemcybersecurityassist.com with the subject line \"Cookie consent withdrawal\".", + ].map((item) => ( +
  • + + {item} +
  • + ))} +
+

+ Withdrawing consent for non-essential cookies does not affect the lawfulness of any + processing carried out before withdrawal, nor does it prevent access to essential + platform functionality. +

+
+ + {/* Third parties */} +
+

Third-party technologies

+

+ Some cookies or similar technologies may be set by third-party subprocessors (such as + cloud hosting or analytics providers) only where those services are active in a given + deployment. GEM Enterprise does not control the cookie practices of external sites + linked from this platform. Refer to each provider's privacy notice for details. +

+
+ + {/* Policy changes */} +
+

Policy changes

+

+ This policy may be updated to reflect changes to our technologies or legal obligations. + Material changes will be communicated through the platform interface. The effective date + at the top of this page indicates when the current version took effect. +

+
+ + {/* Contact */} +
+

Cookie & privacy contact

+

+ For questions about this Cookie Policy or to exercise your rights, contact the GEM + Enterprise Privacy team: +

+ + privacy@gemcybersecurityassist.com + +
+
+ ); +} From 3ee94cdfe09153ca775f21281baa2fc4047545dd Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 12:52:23 +0100 Subject: [PATCH 3/4] =?UTF-8?q?feat(auth):=20forgot-password=20=E2=80=94?= =?UTF-8?q?=20idle/loading/success/error/rate-limit/expired=20states,=20a1?= =?UTF-8?q?1y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/forgot-password/page.tsx | 221 +++++++++++++++++++++++++++++-- 1 file changed, 210 insertions(+), 11 deletions(-) diff --git a/src/app/forgot-password/page.tsx b/src/app/forgot-password/page.tsx index c611df3..4aabea5 100644 --- a/src/app/forgot-password/page.tsx +++ b/src/app/forgot-password/page.tsx @@ -1,20 +1,219 @@ "use client"; + import { useSearchParams } from "next/navigation"; -import { useState } from "react"; +import { useState, useRef } from "react"; import Link from "next/link"; +type FormState = "idle" | "loading" | "success" | "error" | "rate-limit" | "expired"; + export default function ForgotPasswordPage() { const params = useSearchParams(); + const initialState: FormState = params.get("expired") === "1" ? "expired" : "idle"; + const [email, setEmail] = useState(""); - const [state, setState] = useState<"idle"|"loading"|"success"|"error"|"rate-limit">(params.get("expired") ? "error" : "idle"); - const [message, setMessage] = useState(params.get("expired") ? "Your reset link has expired. Request a new secure link below." : ""); - async function submit(e: React.FormEvent) { - e.preventDefault(); setState("loading"); setMessage(""); - const res = await fetch("/api/auth/forgot-password", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email }) }); - const data = await res.json().catch(() => ({})); - if (res.status === 429) { setState("rate-limit"); setMessage(`Too many requests. Try again in ${data.retryAfterSeconds ?? 60} seconds.`); return; } - if (!res.ok) { setState("error"); setMessage(data.error ?? "We could not process the request."); return; } - setState("success"); setMessage(data.message); + const [state, setState] = useState(initialState); + const [message, setMessage] = useState(""); + const [retryAfter, setRetryAfter] = useState(null); + const inputRef = useRef(null); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (state === "loading") return; + setState("loading"); + setMessage(""); + setRetryAfter(null); + + try { + const res = await fetch("/api/auth/forgot-password", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ email: email.trim() }), + }); + + const data = await res.json().catch(() => ({})); + + if (res.status === 429) { + const secs = data.retryAfterSeconds ?? 60; + setRetryAfter(secs); + setState("rate-limit"); + return; + } + if (!res.ok) { + setState("error"); + setMessage(data.error ?? "We could not process your request. Please try again."); + return; + } + + setState("success"); + } catch { + setState("error"); + setMessage("A network error occurred. Please check your connection and try again."); + } + } + + function handleTryAgain() { + setState("idle"); + setMessage(""); + setRetryAfter(null); + setTimeout(() => inputRef.current?.focus(), 50); + } + + return ( +
+
+ {/* Card */} +
+ + {/* Header */} +

+ Account recovery +

+

+ {state === "success" ? "Check your email" : "Forgot password"} +

+ + {/* ── Success state ─────────────────────────────────── */} + {state === "success" && ( +
+
+ +
+

Instructions sent

+

+ If an active GEM Enterprise account exists for{" "} + {email}, password reset instructions + have been sent. Please check your inbox and spam folder. +

+
+
+
    +
  • The link expires in 30 minutes.
  • +
  • Do not share the reset link with anyone.
  • +
  • GEM Enterprise will never ask for your password by email.
  • +
+ +
+ )} + + {/* ── Rate-limit state ──────────────────────────────── */} + {state === "rate-limit" && ( +
+
+ +
+

Too many requests

+

+ For security, password reset requests are rate-limited. + {retryAfter !== null && ( + <> Please wait {retryAfter} seconds before trying again. + )} +

+
+
+ +
+ )} + + {/* ── Expired-link banner (idle) ─────────────────────── */} + {state === "expired" && ( + <> +
+ +
+

Reset link expired

+

+ Your password reset link has expired (links are valid for 30 minutes). Enter + your email below to request a new one. +

+
+
+ {renderForm()} + + )} + + {/* ── Idle / error state ────────────────────────────── */} + {(state === "idle" || state === "loading" || state === "error") && ( + <> +

+ Enter your account email address. For security, we do not confirm whether an email + is registered. +

+ + {state === "error" && message && ( +
+ +

{message}

+
+ )} + + {renderForm()} + + )} + + {/* Return to login */} + {state !== "rate-limit" && ( +

+ + ← Return to client login + +

+ )} +
+ + {/* Security note */} +

+ GEM Enterprise uses encrypted, time-limited reset tokens. We will never ask for your + password via email or phone. +

+
+
+ ); + + function renderForm() { + return ( +
+
+ + setEmail(e.target.value)} + disabled={state === "loading"} + placeholder="you@example.com" + className="mt-2 w-full rounded-xl border border-white/10 bg-slate-950 px-4 py-3 text-white placeholder-slate-500 focus:border-cyan-400 focus:outline-none focus:ring-2 focus:ring-cyan-400/30 disabled:opacity-50 transition-colors" + /> +
+ +
+ ); } - return

Account recovery

Forgot password

Enter your account email. For security, responses do not reveal whether an email is registered.

{message &&
{message}
}

Return to client login

; } From a0763ebbc993187d29267095fa5dd153344aba86 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 12:52:24 +0100 Subject: [PATCH 4/4] =?UTF-8?q?feat(trust):=20trust=20center=20=E2=80=94?= =?UTF-8?q?=20security=20overview,=20data=20protection,=20disclosure,=20co?= =?UTF-8?q?mpliance,=20subprocessors,=20VDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/trust-center/page.tsx | 320 +++++++++++++++++++++++++++++++++- 1 file changed, 316 insertions(+), 4 deletions(-) diff --git a/src/app/trust-center/page.tsx b/src/app/trust-center/page.tsx index 6e6bc21..a7488f5 100644 --- a/src/app/trust-center/page.tsx +++ b/src/app/trust-center/page.tsx @@ -1,6 +1,318 @@ import type { Metadata } from "next"; import Link from "next/link"; -export const metadata: Metadata = { title: "Trust Center", description: "Security, data protection, compliance mapping, disclosure, and due diligence for GEM Enterprise." }; -const mappings=["SOC 2 controls mapped; certification in progress where applicable.","ISO 27001 control alignment designed for future certification readiness.","NIST CSF functions mapped to identify, protect, detect, respond, and recover operations.","GDPR privacy principles supported for applicable data subject requests.","HIPAA safeguards considered for engagements involving regulated health information; GEM does not claim covered-entity status by default.","CMMC practices mapped for defense-supply-chain readiness; certification is not claimed unless separately evidenced."]; -export default function TrustCenterPage(){return

Trust Center

Security and compliance overview

GEM Enterprise is designed to align with enterprise security, privacy, evidence-retention, and vendor due-diligence expectations for qualified clients. Formal attestations are provided only when available and upon verified client request.

Defense-in-depth access controls, encrypted transport, protected session cookies, role-based authorization, audit logging, and KYC-gated workflows.Report suspected vulnerabilities to security@gemcybersecurityassist.com. Provide reproducible details and avoid accessing client data.Client data is handled under least-privilege access, retention controls, and documented operational review. Evidence records follow the platform retention model.Cloud hosting, email delivery, payments, analytics, and AI providers may be used only when configured for an engagement. The current subprocessor list is available upon verified client request.

Compliance mapping

    {mappings.map(m=>
  • {m}
  • )}

Vendor due diligence request

Qualified prospects and clients may request security questionnaires, architecture summaries, insurance evidence, and available attestations.

Request due diligence materials

Status page: placeholder pending connected uptime provider.

} -function Card({title,children}:{title:string;children:React.ReactNode}){return

{title}

{children}

} + +export const metadata: Metadata = { + title: "Trust Center | GEM Enterprise", + description: + "Security overview, data protection, responsible disclosure, compliance mapping, subprocessors, and vendor due diligence information for GEM Enterprise.", +}; + +/* ─── Data ──────────────────────────────────────────────────────────────── */ + +const securityPillars = [ + { + icon: "🔒", + title: "Access controls", + body: "Role-based access control (RBAC), KYC-gated onboarding, least-privilege permissions, and MFA enforcement for privileged accounts.", + }, + { + icon: "🔐", + title: "Data in transit", + body: "All platform traffic is encrypted via TLS 1.2+ with HSTS enforcement. Cleartext communication is not permitted.", + }, + { + icon: "🗃️", + title: "Data at rest", + body: "Sensitive fields are encrypted at rest. Database access is restricted by network policy and authenticated credentials.", + }, + { + icon: "📋", + title: "Audit logging", + body: "Authentication events, privileged actions, and data access are recorded with immutable audit logs for investigative and compliance use.", + }, + { + icon: "🛡️", + title: "Application security", + body: "Security headers (CSP, HSTS, X-Frame-Options), CSRF protection, rate limiting, and input validation are applied at the application layer.", + }, + { + icon: "🔍", + title: "Vulnerability management", + body: "Dependencies are monitored for known vulnerabilities. Critical patches are prioritised through a documented remediation process.", + }, +]; + +const complianceMappings = [ + { + framework: "SOC 2", + detail: + "Controls designed to align with SOC 2 Trust Services Criteria (Security, Availability, Confidentiality). Formal attestation available upon verified client request where applicable.", + }, + { + framework: "ISO 27001", + detail: + "Information security management controls mapped to ISO/IEC 27001 Annex A domains. Certification readiness maintained; formal certification in progress where applicable.", + }, + { + framework: "NIST CSF", + detail: + "Security functions mapped across Identify, Protect, Detect, Respond, and Recover domains of the NIST Cybersecurity Framework v2.0.", + }, + { + framework: "GDPR", + detail: + "Privacy principles (lawfulness, purpose limitation, data minimisation, accuracy, storage limitation, integrity) supported for applicable data subject requests and EU-originated processing.", + }, + { + framework: "HIPAA", + detail: + "Administrative, physical, and technical safeguards considered for engagements that involve regulated health information. GEM does not assert covered-entity status by default — applicability is scoped per engagement.", + }, + { + framework: "CMMC", + detail: + "CMMC practices mapped for defence supply-chain readiness assessments. Formal certification is not claimed unless separately evidenced for a specific engagement scope.", + }, +]; + +const subprocessorCategories = [ + { category: "Cloud infrastructure", example: "Hosting, CDN, object storage, serverless runtime" }, + { category: "Email delivery", example: "Transactional and notification email services" }, + { category: "Payment processing", example: "Secure card and ACH payment handling" }, + { category: "Analytics", example: "Aggregate, privacy-respecting site performance analytics" }, + { category: "AI / LLM services", example: "AI inference providers used for platform intelligence features" }, + { category: "Identity / KYC", example: "Identity verification and document check services" }, +]; + +/* ─── Component ─────────────────────────────────────────────────────────── */ + +function Card({ title, children, className = "" }: { title: string; children: React.ReactNode; className?: string }) { + return ( +
+

{title}

+
{children}
+
+ ); +} + +export default function TrustCenterPage() { + return ( +
+ + {/* ── Header ──────────────────────────────────────────────── */} +

Trust Center

+

+ Security & compliance overview +

+

+ GEM Enterprise is designed to align with enterprise security, privacy, evidence-retention, + and vendor due-diligence expectations for qualified institutional clients. This page is the + single reference for our security posture, data handling commitments, responsible + disclosure process, and compliance mapping. Formal attestations are provided only when + available and upon verified client request. +

+ + {/* ── Security overview ───────────────────────────────────── */} +
+

Security overview

+

+ GEM Enterprise applies a defence-in-depth model across infrastructure, application, and + operational layers. +

+
+ {securityPillars.map((p) => ( +
+

{p.icon}

+

{p.title}

+

{p.body}

+
+ ))} +
+
+ + {/* ── Data protection ──────────────────────────────────────── */} +
+

Data protection

+
+ + Client data is processed under least-privilege access, purpose-limitation controls, and + documented retention schedules. Evidence records follow the platform retention model + and are not used for purposes beyond the original engagement scope. + + + Individuals whose data is processed through GEM Enterprise may submit access, + correction, deletion, or portability requests. Requests are reviewed within applicable + statutory periods. Contact{" "} + + privacy@gemcybersecurityassist.com + + . + + + Operational data is retained only as long as necessary for the stated purpose or as + required by applicable law. Secure deletion procedures are applied to data that has + passed its retention period. + + + A documented incident response procedure covers detection, containment, eradication, + recovery, and notification. Affected clients are notified in accordance with + contractual and regulatory obligations. + +
+
+ + {/* ── Responsible disclosure ───────────────────────────────── */} +
+

Responsible disclosure

+
+

+ GEM Enterprise welcomes reports of potential security vulnerabilities from the research + community and clients. To report a suspected issue: +

+
    + {[ + [ + "Email us", + <> + Send a detailed report to{" "} + + security@gemcybersecurityassist.com + + . Encrypt sensitive details using our PGP key (available on request). + , + ], + [ + "Include reproducible steps", + "Provide affected URLs, request/response examples, and proof-of-concept details sufficient to reproduce the issue.", + ], + [ + "Do not access client data", + "Testing must not involve accessing, modifying, or exfiltrating real client records or production data.", + ], + [ + "Allow time to respond", + "We aim to acknowledge reports within 2 business days and provide an initial assessment within 10 business days.", + ], + [ + "Coordinated disclosure", + "Please allow us a reasonable remediation window before public disclosure. We are committed to transparent, coordinated disclosure practices.", + ], + ].map(([title, body]) => ( +
  • + + + {title}:{" "} + {body as React.ReactNode} + +
  • + ))} +
+
+
+ + {/* ── Compliance mapping ───────────────────────────────────── */} +
+

Compliance mapping

+

+ The following frameworks are referenced in our security and privacy programmes. Mapping + to a framework does not constitute certification unless separately evidenced. +

+
+ {complianceMappings.map((m) => ( +
+

{m.framework}

+

{m.detail}

+
+ ))} +
+
+ + {/* ── Subprocessors ────────────────────────────────────────── */} +
+

Subprocessors

+

+ GEM Enterprise engages third-party subprocessors only where those services are necessary + for platform operations or a specific client engagement. The categories below represent + the types of subprocessors that may be engaged. A named subprocessor list is available + upon verified client request. +

+
+
+
Category
+
Use
+
+ {subprocessorCategories.map((s, i) => ( +
+
{s.category}
+
{s.example}
+
+ ))} +
+

+ To request the full named subprocessor list, email{" "} + + privacy@gemcybersecurityassist.com + + . +

+
+ + {/* ── Vendor due diligence ─────────────────────────────────── */} +
+
+

Procurement

+

Vendor due diligence request

+

+ Qualified prospects and existing clients may request supporting documentation for + vendor due diligence processes, including: +

+
    + {[ + "Security questionnaire responses (SIG, CAIQ, or custom)", + "Architecture and data-flow summaries", + "Business continuity and disaster recovery overview", + "Insurance certificates (where applicable)", + "Available attestation or audit evidence", + "Data processing addendum (DPA)", + ].map((item) => ( +
  • + + {item} +
  • + ))} +
+

+ Documentation is provided under NDA or equivalent confidentiality terms to verified + institutional clients and qualified prospects only. +

+ + Request due diligence package + +
+
+ + {/* ── Security contact ─────────────────────────────────────── */} +
+ + For vulnerability reports, security incidents, or platform security questions:{" "} + + security@gemcybersecurityassist.com + + + + For data subject requests, privacy questions, subprocessor requests, or DPA enquiries:{" "} + + privacy@gemcybersecurityassist.com + + +
+ +
+ ); +}