From df72a74e67a130bef3cb46e4b0e51f351b8c4759 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Thu, 9 Jul 2026 17:04:23 +0000 Subject: [PATCH 01/20] fix(trust): clearly label community hub preview data --- src/app/community-hub/layout.tsx | 33 ++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/app/community-hub/layout.tsx b/src/app/community-hub/layout.tsx index de48d874..dcca27f2 100644 --- a/src/app/community-hub/layout.tsx +++ b/src/app/community-hub/layout.tsx @@ -1,16 +1,41 @@ import type { Metadata } from "next"; import type { ReactNode } from "react"; +import { AlertTriangle } from "lucide-react"; import { HubShell } from "@/components/hub/HubShell"; export const metadata: Metadata = { title: { - default: "Community Hub", - template: "%s | GEM Community Hub", + default: "Community Preview", + template: "%s | GEM Community Preview", }, description: - "GEM Community Hub — a private, verified network for partners, operators, investors, clients, and advisors executing across jurisdictions.", + "Preview of planned GEM Enterprise community capabilities. Sample profiles, organizations, events, statistics, and opportunities are demonstration data.", + robots: { + index: false, + follow: false, + nocache: true, + }, }; export default function CommunityHubLayout({ children }: { children: ReactNode }) { - return {children}; + return ( + + + {children} + + ); } From 0a38caafeeae41822f0839280e631474c78a2637 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Thu, 9 Jul 2026 17:04:49 +0000 Subject: [PATCH 02/20] fix(trust): stop presenting static intelligence as live --- src/app/intel/page.tsx | 241 ++++++++++++++++++++++++++--------------- 1 file changed, 156 insertions(+), 85 deletions(-) diff --git a/src/app/intel/page.tsx b/src/app/intel/page.tsx index fe1eff50..4f8d45e3 100644 --- a/src/app/intel/page.tsx +++ b/src/app/intel/page.tsx @@ -1,94 +1,136 @@ import Image from "next/image"; import Link from "next/link"; -import { Shield, AlertTriangle, Globe, Eye, Zap, ArrowRight, Activity } from "lucide-react"; +import { + Shield, + AlertTriangle, + Globe, + Eye, + ArrowRight, + BookOpenCheck, +} from "lucide-react"; import { Badge } from "@/components/ui/badge"; export const metadata = { - title: "Threat Intelligence | GEM Enterprise", - description: "Live threat intelligence, CVE advisories, dark web monitoring reports, and security bulletins from GEM Enterprise.", + title: "Threat Intelligence Preview", + description: + "A clearly labelled preview of GEM Enterprise threat-intelligence reporting and advisory presentation.", }; const categories = [ - { icon: Shield, label: "Cyber Threats", color: "cyan", count: 14 }, - { icon: AlertTriangle, label: "Critical CVEs", color: "red", count: 3 }, - { icon: Eye, label: "Dark Web Alerts", color: "purple", count: 7 }, - { icon: Globe, label: "Geopolitical", color: "amber", count: 5 }, + { icon: Shield, label: "Cyber examples", color: "cyan", count: 14 }, + { icon: AlertTriangle, label: "CVE examples", color: "red", count: 3 }, + { icon: Eye, label: "Exposure examples", color: "purple", count: 7 }, + { icon: Globe, label: "Risk examples", color: "amber", count: 5 }, ]; -const featuredThreats = [ +const exampleBriefs = [ { severity: "CRITICAL", - title: "Active Exploitation of Palo Alto PAN-OS Authentication Bypass", - summary: "CVE-2025-0108 actively exploited in the wild. Unauthenticated attackers gaining management interface access. GEM clients on affected firmware versions have been individually notified.", + title: "Example advisory: PAN-OS authentication bypass", + summary: + "Demonstration of how a verified vendor advisory and exploited-vulnerability notice could be summarized for an authorized client. This card is not a current alert and must not be used for security decisions.", category: "Infrastructure", - timestamp: "2 hours ago", - affected: "Network Perimeter", + referenceDate: "Reference advisory: February 2025", + affected: "Network perimeter", }, { severity: "HIGH", - title: "Credential Stuffing Campaign Targeting Financial Institution Portals", - summary: "Large-scale automated credential stuffing operation detected against major US and UK banking portals. 2.3M validated credential pairs circulating on dark web markets. Wire fraud risk elevated.", + title: "Example scenario: credential-stuffing activity", + summary: + "Demonstration content showing how account-takeover indicators, affected services, recommended controls, and escalation guidance may be presented after source verification.", category: "Financial", - timestamp: "6 hours ago", - affected: "Financial Services", + referenceDate: "Demonstration scenario", + affected: "Identity and access", }, { severity: "HIGH", - title: "Real Estate Wire Fraud: Title Company Email Compromise Campaign", - summary: "Organized BEC campaign targeting real estate title companies in California, Texas, and New York. Attacker pattern: compromise title agent email, redirect closing wire instructions 24-48 hours before close.", + title: "Example scenario: real-estate payment diversion", + summary: + "Demonstration content showing a business-email-compromise pattern and the verification steps that may be included in a client briefing. No active campaign is asserted by this page.", category: "Real Estate", - timestamp: "1 day ago", - affected: "Property Transactions", + referenceDate: "Demonstration scenario", + affected: "Property transactions", }, { severity: "MEDIUM", - title: "QakBot Variant with New C2 Infrastructure Detected", - summary: "Updated QakBot variant using fast-flux DNS and TLS-encrypted C2 beaconing to evade detection. Delivered via malicious OneNote documents in targeted spear-phishing campaigns.", + title: "Example scenario: malware command-and-control changes", + summary: + "Demonstration content showing how malware infrastructure changes, detection guidance, and source confidence may be communicated in a future verified feed.", category: "Malware", - timestamp: "2 days ago", - affected: "Enterprise Endpoints", + referenceDate: "Demonstration scenario", + affected: "Enterprise endpoints", }, ]; const severityColors = { - "CRITICAL": "text-red-400 border-red-500/30 bg-red-500/10", - "HIGH": "text-orange-400 border-orange-500/30 bg-orange-500/10", - "MEDIUM": "text-amber-400 border-amber-500/30 bg-amber-500/10", - "LOW": "text-green-400 border-green-500/30 bg-green-500/10", + CRITICAL: "text-red-400 border-red-500/30 bg-red-500/10", + HIGH: "text-orange-400 border-orange-500/30 bg-orange-500/10", + MEDIUM: "text-amber-400 border-amber-500/30 bg-amber-500/10", + LOW: "text-green-400 border-green-500/30 bg-green-500/10", }; export default function IntelPage() { return (
+
+
+
+
- {/* HERO */} -
+
- GEM Enterprise live threat intelligence operations room — multiple curved monitors displaying real-time cybersecurity advisories, global CVE bulletins, dark web monitoring alerts, and structured threat timelines. An analyst reviews a tablet in the foreground while wall screens update with live data feeds. + Illustrative threat-intelligence interface concept
-
-
-
- 3 CRITICAL Threats Active +
+
+
-

Threat Intelligence

-

Live advisories, CVE tracking, dark web monitoring, and geopolitical risk from GEM's 24/7 intelligence operations.

+

+ Threat Intelligence Preview +

+

+ A transparent preview of how sourced advisories, exposure findings, and + risk briefings may be presented after a verified data pipeline is activated. +

- {/* CATEGORY COUNTERS */} -
-
-
- {categories.map((cat, i) => { +
+
+
+ {categories.map((cat) => { const Icon = cat.icon; - const c = cat.color === "cyan" ? "text-cyan-400 border-cyan-500/20 bg-cyan-500/5" : cat.color === "red" ? "text-red-400 border-red-500/20 bg-red-500/5" : cat.color === "purple" ? "text-purple-400 border-purple-500/20 bg-purple-500/5" : "text-amber-400 border-amber-500/20 bg-amber-500/5"; + const classes = + cat.color === "cyan" + ? "text-cyan-400 border-cyan-500/20 bg-cyan-500/5" + : cat.color === "red" + ? "text-red-400 border-red-500/20 bg-red-500/5" + : cat.color === "purple" + ? "text-purple-400 border-purple-500/20 bg-purple-500/5" + : "text-amber-400 border-amber-500/20 bg-amber-500/5"; + return ( -
- +
+
- {/* DARK WEB MONITORING FEATURE */} -
-
-
- GEM dark web monitoring analyst in a tense dimly-lit room reviewing credential leak databases, dark web forum activity, and compromised IP address maps — representing GEM's continuous dark web surveillance operation on behalf of enterprise clients +
+
+
+ Illustrative exposure-monitoring interface concept
-
- Dark Web Active +
+ + Illustrative image +
- Dark Web Monitoring -

Your Exposure, Found Before Attackers Act

-

GEM continuously monitors dark web forums, paste sites, credential markets, and breach databases — scanning specifically for your organization's domain, email patterns, credential pairs, and sensitive data signatures.

-

When a match is found, you receive an immediate alert with context: what was exposed, where it appeared, and what action to take — before attackers can weaponize it.

- - Learn About Cyber Fund + + Planned exposure reporting + +

+ Source verification before publication +

+

+ Production intelligence must identify its source, publication time, retrieval + time, confidence, affected products, and expiry or review state. Stale data must + be visibly marked and must never receive automatically changing relative-time labels. +

+

+ Client-specific exposure monitoring will be described as active only after an + authorized monitoring provider, alert-delivery path, escalation process, and + service agreement have been verified. +

+ + Discuss intelligence services
- {/* FEATURED THREATS */} -
-
-
-
-

Active Threat Advisories

-

Updated continuously by GEM intelligence analysts

-
-
- - Live Feed -
+
+
+
+

Example advisory cards

+

+ Static examples only. Each card is deliberately labelled and uses an absolute + reference date or demonstration status. +

- {featuredThreats.map((threat, i) => ( -
-
- - ⚠ {threat.severity} + {exampleBriefs.map((brief) => ( +
+
+ + Example {brief.severity} + + + {brief.category} - {threat.category} - {threat.timestamp} + {brief.referenceDate}
-

{threat.title}

-

{threat.summary}

-
Affected: {threat.affected}
-
+

{brief.title}

+

{brief.summary}

+
Example affected area: {brief.affected}
+ ))}
-
-

Full intelligence feed — authenticated client access only

- - Request Portal Access +
+

+ No public page should be treated as an emergency security-alert channel. +

+ + Contact the security team
From 0b375b2d06d2ef4a525a6b513627ae6961b916df Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Thu, 9 Jul 2026 17:05:16 +0000 Subject: [PATCH 03/20] fix(services): remove unsupported guarantees and keep detail links honest --- src/app/services/page.tsx | 203 ++++++++++++++++++++++++-------------- 1 file changed, 127 insertions(+), 76 deletions(-) diff --git a/src/app/services/page.tsx b/src/app/services/page.tsx index 15cbed12..f39e65ea 100644 --- a/src/app/services/page.tsx +++ b/src/app/services/page.tsx @@ -1,79 +1,88 @@ import Link from "next/link"; import Image from "next/image"; -import { Shield, Zap, Lock, Eye, Building2, ArrowRight, Globe, Users } from "lucide-react"; +import { + Shield, + Zap, + Lock, + Eye, + Building2, + ArrowRight, + Globe, +} from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; export const metadata = { - title: "Services | GEM Enterprise", - description: "Institutional-grade cybersecurity, financial security, and real estate protection services for qualified clients.", + title: "Services", + description: + "Cybersecurity, compliance, financial-security coordination, and property-risk services, subject to scope, eligibility, provider availability, and signed agreements.", }; const services = [ { icon: Shield, - title: "24/7 Threat Monitoring & SOC", + title: "Managed Threat Monitoring", slug: "threat-monitoring", - desc: "Continuous AI-powered surveillance with certified analysts operating on a follow-the-sun model. Sub-6-minute mean time to acknowledge (MTTA) with zero coverage gaps across North America, EMEA, and APAC.", + desc: "Monitoring, triage, and escalation services can be arranged for eligible clients through an approved service scope. Coverage hours, tooling, staffing, response targets, and data sources are defined in the signed agreement.", img: "https://media.base44.com/images/public/69d42975b7b1794c3dc01661/5c6e6baaf_generated_image.png", - imgAlt: "GEM Enterprise Security Operations Center — analysts at curved 6-monitor workstations monitoring live SIEM dashboards, global attack maps, and threat feeds around the clock", - tags: ["SIEM", "Log Correlation", "Real-Time Alerts"], - tier: "Enterprise", + imgAlt: "Illustrative managed security monitoring interface", + tags: ["Monitoring", "Triage", "Escalation"], + tier: "Contracted service", color: "cyan", }, { icon: Zap, - title: "Incident Response", + title: "Incident Response Coordination", slug: "incident-response", - desc: "Guaranteed 4-hour activation SLA. Rapid containment, eradication, and recovery. Full digital forensics, chain-of-custody evidence management, and post-incident executive briefing.", + desc: "Incident triage, containment planning, evidence-preservation guidance, and recovery coordination are available according to contracted coverage. Activation windows are service-specific and are not guaranteed by this public page.", img: "https://media.base44.com/images/public/69d42975b7b1794c3dc01661/c974a8817_generated_image.png", - imgAlt: "GEM incident response war room — 8 cybersecurity analysts at circular workstation formation with large wall screens showing attack timeline, affected systems heatmap, and live network anomaly graphs", - tags: ["4hr SLA", "Digital Forensics", "Evidence Preservation"], - tier: "All Tiers", + imgAlt: "Illustrative incident response coordination room", + tags: ["Triage", "Evidence", "Recovery"], + tier: "Scope required", color: "red", }, { icon: Eye, - title: "Dark Web Monitoring", + title: "Exposure & Dark-Web Monitoring", slug: "dark-web", - desc: "Continuous surveillance of dark web forums, paste sites, credential markets, and breach databases. Instant alerts on exposed credentials, IP ranges, and organizational data.", + desc: "Authorized exposure monitoring may include approved breach-data, credential, domain, and public-risk sources. Data coverage, provider limitations, alert timing, and escalation routes are defined before activation.", img: "https://media.base44.com/images/public/69d42975b7b1794c3dc01661/86e283cd8_generated_image.png", - imgAlt: "GEM dark web monitoring analyst in a dark room illuminated only by monitor glow, reviewing credential leak databases and dark web forum activity on behalf of enterprise clients", - tags: ["Credential Leaks", "IP Monitoring", "Breach Alerts"], - tier: "Enterprise", + imgAlt: "Illustrative exposure-monitoring interface", + tags: ["Exposure", "Credentials", "Alerts"], + tier: "Provider dependent", color: "purple", }, { icon: Lock, - title: "Red Team & Penetration Testing", + title: "Security Assessment & Testing", slug: "red-team", - desc: "Full-scope adversarial simulations covering network penetration, web application security, social engineering, and physical security. Board-level debrief and remediation roadmap included.", + desc: "Authorized security assessments may cover applications, infrastructure, configuration, and selected human-process controls. Testing requires written scope, ownership verification, rules of engagement, and approved timing.", img: "https://media.base44.com/images/public/69d42975b7b1794c3dc01661/ca12688fe_generated_image.png", - imgAlt: "GEM red team penetration tester at dual-monitor workstation running vulnerability scanner with color-coded risk ratings and terminal commands — professional offensive security setup", - tags: ["Pentest", "Social Engineering", "Kill Chain Simulation"], - tier: "Enterprise", + imgAlt: "Illustrative authorized security testing workstation", + tags: ["Assessment", "Validation", "Remediation"], + tier: "Written authorization", color: "orange", }, { icon: Building2, - title: "Asset Recovery & Physical Security", + title: "Asset & Property Risk Coordination", slug: "asset-recovery", - desc: "High-value physical and digital asset protection and global recovery operations. Coordinated with Alliance Trust Realty for cross-jurisdiction property and financial asset recovery.", - img: "https://media.base44.com/images/public/69d42975b7b1794c3dc01661/1f7e5fb1b_generated_image.png", - imgAlt: "Alliance Trust Realty institutional real estate and asset recovery — luxury commercial property tower at dusk representing the physical asset management and recovery operations division of GEM Enterprise", - tags: ["Physical Security", "Cross-Jurisdiction", "Asset Recovery"], - tier: "Elite", + desc: "GEM can coordinate risk review, documentation, specialist referrals, and authorized recovery support where appropriate. Legal authority, ownership, jurisdiction, provider capability, and engagement limits are verified before action.", + img: "https://media.base44.com/images/public/69d42975b7b1794c3dc01661/1f7e5fb1b7b1794c3dc01661/1f7e5fb1b_generated_image.png", + imgAlt: "Illustrative property and asset-risk management image", + tags: ["Risk Review", "Referrals", "Documentation"], + tier: "Eligibility review", color: "amber", }, { icon: Globe, - title: "Federal Compliance & Regulatory", + title: "Compliance Readiness Support", slug: "federal-compliance", - desc: "Specialized regulatory navigation for NIST SP 800-171, CMMC 2.0, SOC 2, ISO 27001, HIPAA, GDPR, and sector-specific mandates. Gap analysis, policy development, and audit readiness.", + desc: "Readiness support may include gap assessment, control mapping, policy development, evidence planning, and remediation tracking. Framework alignment is not certification and does not replace legal or accredited audit advice.", img: "https://media.base44.com/images/public/69d42975b7b1794c3dc01661/2e5c40e81_generated_image.png", - imgAlt: "GEM compliance officer at executive desk reviewing multi-screen compliance dashboard showing SOC 2 Type II, ISO 27001, NIST CSF, and HIPAA compliance scorecards with green and amber indicators", - tags: ["NIST 800-171", "CMMC 2.0", "ISO 27001"], - tier: "Enterprise", + imgAlt: "Illustrative compliance readiness dashboard", + tags: ["NIST", "SOC 2", "ISO 27001"], + tier: "Readiness support", color: "blue", }, ]; @@ -81,72 +90,114 @@ const services = [ export default function ServicesPage() { return (
- - {/* HERO */} -
+
- GEM Enterprise Security Operations Center running 24/7 global threat monitoring + Illustrative enterprise security operations interface
-
- Our Services -

Enterprise Service Suite

-

- Six interconnected disciplines forming a complete institutional security framework — from proactive threat hunting and dark web surveillance to regulatory compliance and physical asset recovery. +

+ + Service capabilities + +

+ Enterprise Service Suite +

+

+ Services are activated only after scope, eligibility, staffing, provider, + jurisdiction, security, and contractual requirements have been confirmed. + Public descriptions do not create an SLA or guarantee availability.

- {/* SERVICES GRID — alternating image layout */} -
+
- {services.map((svc, i) => { - const Icon = svc.icon; - const isEven = i % 2 === 0; - const accentColor = svc.color === "cyan" ? "text-cyan-400 border-cyan-500/30 bg-cyan-500/10" : svc.color === "red" ? "text-red-400 border-red-500/30 bg-red-500/10" : svc.color === "purple" ? "text-purple-400 border-purple-500/30 bg-purple-500/10" : svc.color === "orange" ? "text-orange-400 border-orange-500/30 bg-orange-500/10" : svc.color === "amber" ? "text-amber-400 border-amber-500/30 bg-amber-500/10" : "text-blue-400 border-blue-500/30 bg-blue-500/10"; + {services.map((service, index) => { + const Icon = service.icon; + const isEven = index % 2 === 0; + const accentColor = + service.color === "cyan" + ? "text-cyan-400 border-cyan-500/30 bg-cyan-500/10" + : service.color === "red" + ? "text-red-400 border-red-500/30 bg-red-500/10" + : service.color === "purple" + ? "text-purple-400 border-purple-500/30 bg-purple-500/10" + : service.color === "orange" + ? "text-orange-400 border-orange-500/30 bg-orange-500/10" + : service.color === "amber" + ? "text-amber-400 border-amber-500/30 bg-amber-500/10" + : "text-blue-400 border-blue-500/30 bg-blue-500/10"; + return ( -
+
- {/* Image */}
- {svc.imgAlt} -
+ {service.imgAlt} +
+ + Illustrative image +
- {/* Content */} -
-
-
- +
+
+
+
- {svc.tier} + + {service.tier} +
-

{svc.title}

-

{svc.desc}

-
- {svc.tags.map(t => {t})} +

+ {service.title} +

+

{service.desc}

+
+ {service.tags.map((tag) => ( + + {tag} + + ))}
- - Learn More + + Review scope and limitations
-
+
); })}
- {/* CTA */} -
-
-

Qualified Clients Only

-

All GEM services are delivered under a KYC-gated, compliance-reviewed engagement model. Begin your application to access the full service suite.

-
- -
From dcd741ecc2b3ebace5bf1ccc6f771ed9814847b8 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Thu, 9 Jul 2026 17:05:40 +0000 Subject: [PATCH 04/20] feat(services): add working service detail routes --- src/app/services/[slug]/page.tsx | 242 +++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 src/app/services/[slug]/page.tsx diff --git a/src/app/services/[slug]/page.tsx b/src/app/services/[slug]/page.tsx new file mode 100644 index 00000000..0a9d79e4 --- /dev/null +++ b/src/app/services/[slug]/page.tsx @@ -0,0 +1,242 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { + AlertTriangle, + ArrowRight, + Building2, + Eye, + Globe, + Lock, + Shield, + Zap, +} from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; + +const services = { + "threat-monitoring": { + icon: Shield, + title: "Managed Threat Monitoring", + summary: + "Monitoring, triage, and escalation support for eligible client environments under a documented statement of work.", + capabilities: [ + "Approved telemetry and alert-source onboarding", + "Triage criteria and escalation routing", + "Periodic reporting and control review", + "Named operational contacts and support windows", + ], + requirements: [ + "Verified authority over the monitored environment", + "Approved data sources and retention terms", + "Confirmed staffing or provider coverage", + "Signed scope, exclusions, and response targets", + ], + }, + "incident-response": { + icon: Zap, + title: "Incident Response Coordination", + summary: + "Structured triage, containment planning, evidence-preservation guidance, and recovery coordination according to contracted coverage.", + capabilities: [ + "Initial incident intake and severity assessment", + "Containment and recovery coordination", + "Evidence-handling and decision documentation", + "Post-incident review and remediation planning", + ], + requirements: [ + "Authorized incident contact and decision-maker", + "Defined systems and jurisdictions in scope", + "Available technical access and evidence sources", + "Contracted activation and escalation terms", + ], + }, + "dark-web": { + icon: Eye, + title: "Exposure & Dark-Web Monitoring", + summary: + "Authorized exposure monitoring using approved providers and sources, with documented limitations and escalation routes.", + capabilities: [ + "Domain and credential-exposure monitoring", + "Source confidence and evidence review", + "Alert validation and recommended next steps", + "Periodic exposure summaries", + ], + requirements: [ + "Confirmed ownership or authority for monitored identifiers", + "Approved provider and lawful data source", + "Documented alert recipients", + "Defined false-positive and escalation process", + ], + }, + "red-team": { + icon: Lock, + title: "Security Assessment & Testing", + summary: + "Authorized assessment of applications, infrastructure, configuration, and selected business processes within explicit rules of engagement.", + capabilities: [ + "Scope and attack-surface review", + "Application and infrastructure testing", + "Control validation and evidence collection", + "Prioritized remediation reporting", + ], + requirements: [ + "Written authorization from the asset owner", + "Approved targets, timing, and exclusions", + "Emergency contact and stop conditions", + "Data-handling and evidence-retention plan", + ], + }, + "asset-recovery": { + icon: Building2, + title: "Asset & Property Risk Coordination", + summary: + "Risk review, documentation support, and specialist coordination where ownership, authority, jurisdiction, and provider capability are verified.", + capabilities: [ + "Ownership and documentation review", + "Risk and fraud-indicator assessment", + "Qualified specialist and provider referrals", + "Case coordination and evidence tracking", + ], + requirements: [ + "Verified identity and legal authority", + "Supporting ownership and transaction records", + "Jurisdiction and legal-scope review", + "Signed engagement and provider acceptance", + ], + }, + "federal-compliance": { + icon: Globe, + title: "Compliance Readiness Support", + summary: + "Gap assessment, control mapping, policy support, and remediation planning for selected frameworks. Readiness support is not certification.", + capabilities: [ + "Current-state gap assessment", + "Control and evidence mapping", + "Policy and procedure support", + "Remediation tracking and audit preparation", + ], + requirements: [ + "Confirmed framework and organizational scope", + "Access to relevant policies and evidence", + "Named control owners", + "Independent legal or accredited audit advice where required", + ], + }, +} as const; + +type ServiceSlug = keyof typeof services; + +export function generateStaticParams() { + return Object.keys(services).map((slug) => ({ slug })); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ slug: string }>; +}): Promise { + const { slug } = await params; + const service = services[slug as ServiceSlug]; + if (!service) return {}; + + return { + title: service.title, + description: service.summary, + }; +} + +export default async function ServiceDetailPage({ + params, +}: { + params: Promise<{ slug: string }>; +}) { + const { slug } = await params; + const service = services[slug as ServiceSlug]; + if (!service) notFound(); + + const Icon = service.icon; + + return ( +
+
+
+ + Service scope + +
+
+
+
+

{service.title}

+

+ {service.summary} +

+
+
+
+
+ +
+
+

Potential capabilities

+
    + {service.capabilities.map((item) => ( +
  • + + {item} +
  • + ))} +
+
+ +
+

Activation requirements

+
    + {service.requirements.map((item) => ( +
  • + + {item} +
  • + ))} +
+
+
+ +
+
+
+
+
+
+ +
+
+
+

Request a scoped assessment

+

+ Tell us the environment, objective, jurisdiction, timeline, and current constraints. + The team will confirm whether the service can be offered and under what conditions. +

+
+ +
+
+
+ ); +} From 935c213ad49b7cb34eec875966be787c9c85e9d0 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Thu, 9 Jul 2026 17:06:01 +0000 Subject: [PATCH 05/20] fix(contact): durably store every public enquiry before success --- src/app/api/contact/route.ts | 165 ++++++++++++++++++++++------------- 1 file changed, 106 insertions(+), 59 deletions(-) diff --git a/src/app/api/contact/route.ts b/src/app/api/contact/route.ts index d4145c0b..954fd0ff 100644 --- a/src/app/api/contact/route.ts +++ b/src/app/api/contact/route.ts @@ -5,21 +5,19 @@ import { getSession } from "@/lib/auth"; import { emitAuditLog } from "@/lib/audit"; import { getRequestContext, badRequest } from "@/lib/api/auth-helpers"; import { rateLimit, rateLimitedResponse } from "@/lib/api/rate-limit"; +import { sendMail } from "@/lib/mail/send"; const schema = z.object({ name: z.string().trim().min(2, "Name must be at least 2 characters").max(120), email: z.string().trim().toLowerCase().email("Invalid email address").max(254), message: z.string().trim().min(10, "Message must be at least 10 characters").max(5_000), subject: z.string().trim().max(200).optional(), - // Optional honeypot field — bots tend to fill every input. If populated we - // accept (200 OK) but silently drop the submission. website: z.string().optional(), }); export async function POST(req: NextRequest) { const { ipAddress, userAgent } = getRequestContext(req); - // Hard rate-limit per IP: 5 contact submissions / hour. const limit = rateLimit(ipAddress, { key: "contact:submit", windowMs: 60 * 60_000, @@ -41,75 +39,124 @@ export async function POST(req: NextRequest) { const { name, email, message, subject, website } = parsed.data; - // Honeypot — return 200 OK to avoid signaling rejection to the bot. + // Honeypot submissions receive a generic success response but are not stored. if (website && website.trim().length > 0) { return NextResponse.json({ ok: true }); } - // Capture the inbound contact as a SupportTicket only when the sender is - // already authenticated. Otherwise we never write to the database — the - // ticket model requires a real userId FK and we will not synthesize one - // (which previously caused FK violations and lost messages). const session = await getSession(); - let ticketId: string | null = null; + const normalizedSubject = subject?.trim() || `Contact from ${name}`; + const baseNotes = [ + `Website contact enquiry`, + `From: ${name} <${email}>`, + `Authenticated: ${session ? `yes (${session.email})` : "no"}`, + "", + message, + ].join("\n"); + + try { + // Persist every genuine public enquiry before reporting success. SupportBooking + // is intentionally used here because it accepts unauthenticated contacts and is + // already part of the production schema. Email is only a notification channel; + // it is not the system of record. + const submission = await db.supportBooking.create({ + data: { + userId: session?.userId, + name, + email, + subject: normalizedSubject, + status: "pending", + notes: `${baseNotes}\n\nNotification delivery: pending`, + }, + select: { id: true }, + }); + + let ticketId: string | null = null; + if (session) { + try { + const ticket = await db.supportTicket.create({ + data: { + userId: session.userId, + subject: normalizedSubject, + description: `From: ${name} <${email}>\n\n${message}`, + status: "open", + priority: "medium", + }, + select: { id: true }, + }); + ticketId = ticket.id; + } catch (error) { + console.error("[contact] failed to create authenticated support ticket", error); + } + } + + let delivery = "not_configured"; + const recipient = + process.env.ADMIN_EMAIL || + process.env.SUPPORT_EMAIL || + process.env.GEM_OWNER_EMAIL; + + if (recipient) { + try { + const result = await sendMail({ + to: recipient, + replyTo: email, + subject: `[GEM Contact] ${normalizedSubject}`, + text: `Submission ID: ${submission.id}\nName: ${name}\nEmail: ${email}\nAuth: ${session ? `yes (${session.email})` : "no"}\nIP: ${ipAddress}\n\n${message}`, + }); + delivery = result.sent + ? "sent" + : "reason" in result + ? result.reason + : "skipped"; + } catch (error) { + delivery = "failed"; + console.error("[contact] failed to send notification email", error); + } + } - if (session) { try { - const ticket = await db.supportTicket.create({ + await db.supportBooking.update({ + where: { id: submission.id }, data: { - userId: session.userId, - subject: subject?.trim() || `Contact from ${name}`, - description: `From: ${name} <${email}>\n\n${message}`, - status: "open", - priority: "medium", + notes: `${baseNotes}\n\nNotification delivery: ${delivery}`, }, - select: { id: true }, }); - ticketId = ticket.id; } catch (error) { - console.error("[contact] failed to persist ticket", error); - // non-fatal — fall through and still email + audit + console.error("[contact] failed to record notification status", error); } - } - await emitAuditLog({ - userId: session?.userId, - action: "admin_action", - resource: "contact_message", - resourceId: ticketId ?? undefined, - metadata: { - authenticated: Boolean(session), - email, - name, - subject: subject ?? null, - ticketId, - }, - ipAddress, - userAgent, - }); + await emitAuditLog({ + userId: session?.userId, + action: "admin_action", + resource: "contact_message", + resourceId: submission.id, + metadata: { + authenticated: Boolean(session), + email, + name, + subject: normalizedSubject, + submissionId: submission.id, + ticketId, + notificationDelivery: delivery, + }, + ipAddress, + userAgent, + }); - // Send notification email if SMTP is configured. We isolate the import in - // the conditional so deployments without nodemailer wired don't pay the - // bundle cost on the cold path. - if (process.env.SMTP_HOST && process.env.ADMIN_EMAIL) { - try { - const nodemailer = await import("nodemailer"); - const transporter = nodemailer.default.createTransport({ - host: process.env.SMTP_HOST, - port: Number(process.env.SMTP_PORT ?? 587), - auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS }, - }); - await transporter.sendMail({ - from: process.env.EMAIL_FROM ?? process.env.SMTP_USER, - to: process.env.ADMIN_EMAIL, - replyTo: email, - subject: `[GEM Contact] ${subject?.trim() || `Message from ${name}`}`, - text: `Name: ${name}\nEmail: ${email}\nAuth: ${session ? `yes (${session.email})` : "no"}\nIP: ${ipAddress}\n\n${message}`, - }); - } catch (error) { - console.error("[contact] failed to send notification email", error); - } + return NextResponse.json({ + ok: true, + submissionId: submission.id, + ticketId, + }); + } catch (error) { + console.error("[contact] failed to persist public enquiry", error); + return NextResponse.json( + { + ok: false, + error: "Your message could not be stored. Please use the published support email.", + }, + { status: 503 }, + ); } - - return NextResponse.json({ ok: true, ticketId }); } From 5ea8db55fc727f0d22ee30e0b65d08577b147402 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Thu, 9 Jul 2026 17:06:27 +0000 Subject: [PATCH 06/20] fix(kyc): fail closed until secure document storage is active --- src/app/kyc/upload/page.tsx | 268 +++++++----------------------------- 1 file changed, 46 insertions(+), 222 deletions(-) diff --git a/src/app/kyc/upload/page.tsx b/src/app/kyc/upload/page.tsx index db9950ac..ecfb4e99 100644 --- a/src/app/kyc/upload/page.tsx +++ b/src/app/kyc/upload/page.tsx @@ -1,242 +1,66 @@ -"use client"; - -import { useState, useRef, useCallback } from "react"; -import { useRouter } from "next/navigation"; import Link from "next/link"; +import { AlertTriangle, ArrowLeft, LockKeyhole } from "lucide-react"; import { Button } from "@/components/ui/button"; -type DocumentSlot = { - id: string; - label: string; - description: string; - required: boolean; -}; - -const DOCUMENT_SLOTS: DocumentSlot[] = [ - { - id: "government-id", - label: "Government-Issued ID", - description: "Passport, driver's license, or national ID card", - required: true, - }, - { - id: "proof-of-address", - label: "Proof of Address", - description: "Utility bill, bank statement, or government letter (within 90 days)", - required: true, - }, - { - id: "financial-statement", - label: "Financial Statement", - description: "Bank statement or brokerage account statement (within 6 months)", - required: true, +export const metadata = { + title: "Secure Document Upload", + robots: { + index: false, + follow: false, }, - { - id: "tax-document", - label: "Tax Document", - description: "Most recent tax return or W-2 / 1099 form", - required: false, - }, - { - id: "entity-documents", - label: "Entity Documents", - description: "Articles of incorporation, trust deed, or operating agreement", - required: false, - }, -]; - -type FileEntry = { - file: File; - name: string; - size: string; }; -function formatBytes(bytes: number): string { - if (bytes < 1024) return `${bytes} B`; - if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; - return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; -} - -function UploadArea({ - slot, - file, - onFile, -}: { - slot: DocumentSlot; - file: FileEntry | null; - onFile: (slotId: string, file: File | null) => void; -}) { - const inputRef = useRef(null); - const [dragging, setDragging] = useState(false); - - const handleDrop = useCallback( - (e: React.DragEvent) => { - e.preventDefault(); - setDragging(false); - const dropped = e.dataTransfer.files[0]; - if (dropped) onFile(slot.id, dropped); - }, - [slot.id, onFile] - ); - - const handleChange = (e: React.ChangeEvent) => { - const selected = e.target.files?.[0] ?? null; - onFile(slot.id, selected); - }; - - return ( -
-
- {slot.label} - {slot.required && ( - Required - )} -
-

{slot.description}

- - {file ? ( -
- -
-

{file.name}

-

{file.size}

-
- -
- ) : ( -
inputRef.current?.click()} - onKeyDown={(e) => e.key === "Enter" && inputRef.current?.click()} - onDragOver={(e) => { e.preventDefault(); setDragging(true); }} - onDragLeave={() => setDragging(false)} - onDrop={handleDrop} - className={`cursor-pointer rounded-lg border-2 border-dashed px-6 py-8 text-center transition-colors ${ - dragging - ? "border-[hsl(var(--electric-cyan))] bg-[hsl(var(--electric-cyan)/0.08)]" - : "border-[hsl(var(--border))] hover:border-[hsl(var(--electric-cyan)/0.5)] hover:bg-[hsl(var(--electric-cyan)/0.03)]" - }`} - aria-label={`Upload ${slot.label}`} - > - -

- Drag & Drop or click to upload -

-

Accepted: PDF, JPG, JPEG, PNG

- -
- )} -
- ); -} - export default function KYCUploadPage() { - const router = useRouter(); - const [files, setFiles] = useState>({}); - const [submitting, setSubmitting] = useState(false); - const [error, setError] = useState(null); - - const handleFile = useCallback((slotId: string, file: File | null) => { - setFiles((prev) => ({ - ...prev, - [slotId]: file - ? { file, name: file.name, size: formatBytes(file.size) } - : null, - })); - }, []); - - const requiredIds = DOCUMENT_SLOTS.filter((s) => s.required).map((s) => s.id); - const allRequiredUploaded = requiredIds.every((id) => !!files[id]); - - const handleSubmit = async () => { - if (!allRequiredUploaded) { - setError("Please upload all required documents before submitting."); - return; - } - setError(null); - setSubmitting(true); - try { - // Stub — build FormData if/when real upload endpoint exists - await fetch("/api/kyc/upload", { method: "POST" }); - router.push("/kyc/review"); - } catch { - router.push("/kyc/review"); - } finally { - setSubmitting(false); - } - }; - return ( -
+
- - Back +
); } From 62252680fcf4d96eede41b4dc0dd321249ebc250 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Thu, 9 Jul 2026 17:06:36 +0000 Subject: [PATCH 07/20] fix(kyc): reject metadata-only document submissions in production --- src/app/api/kyc/documents/route.ts | 167 ++++++----------------------- 1 file changed, 34 insertions(+), 133 deletions(-) diff --git a/src/app/api/kyc/documents/route.ts b/src/app/api/kyc/documents/route.ts index 055598b9..7dd412c5 100644 --- a/src/app/api/kyc/documents/route.ts +++ b/src/app/api/kyc/documents/route.ts @@ -1,136 +1,37 @@ -import { NextRequest, NextResponse } from "next/server"; -import { z } from "zod"; -import { db } from "@/lib/db"; -import { emitAuditLog } from "@/lib/audit"; -import { - requireSession, - getRequestContext, - badRequest, - serverError, -} from "@/lib/api/auth-helpers"; - -// Storage path is generated server-side from the application ID; the client -// only supplies metadata. This prevents an authenticated user from writing -// document rows that point at someone else's storage prefix. - -const ALLOWED_MIME = [ - "application/pdf", - "image/jpeg", - "image/png", - "image/webp", - "image/heic", -]; -const MAX_FILE_SIZE = 25 * 1024 * 1024; // 25 MB - -const createDocumentSchema = z.object({ - applicationId: z.string().min(1, "applicationId is required"), - documentType: z - .string() - .trim() - .min(2) - .max(64) - .regex(/^[a-z0-9_-]+$/i, "documentType must be alphanumeric/underscore"), - fileName: z - .string() - .trim() - .min(1) - .max(255) - // Block path traversal characters at the filename level. - .regex(/^[^/\\]+$/, "fileName must not contain path separators"), - fileSize: z - .number() - .int() - .nonnegative() - .max(MAX_FILE_SIZE, `File exceeds ${MAX_FILE_SIZE} bytes`) - .optional(), - mimeType: z.string().trim().min(1).max(127).optional(), -}); - -export async function POST(req: NextRequest) { +import { NextResponse } from "next/server"; +import { requireSession } from "@/lib/api/auth-helpers"; + +/** + * Production document intake is intentionally disabled until a complete private + * object-storage flow is available. The previous implementation created only a + * database metadata row and did not store, scan, or verify the actual file. That + * could make an application appear to contain documents when no document existed. + */ +export async function POST() { const gate = await requireSession(); - if (!gate.ok) return (gate as { ok: false; response: any }).response; - const session = gate.session; - const { ipAddress, userAgent } = getRequestContext(req); - - let body: unknown; - try { - body = await req.json(); - } catch { - return badRequest("Invalid JSON"); - } - - const parsed = createDocumentSchema.safeParse(body); - if (!parsed.success) { - return badRequest("Validation failed", parsed.error.flatten().fieldErrors); - } - - const { applicationId, documentType, fileName, fileSize, mimeType } = parsed.data; - const safeMime = mimeType && ALLOWED_MIME.includes(mimeType) - ? mimeType - : "application/octet-stream"; - - try { - // Ownership check: only allow attaching documents to the caller's own - // application, and only while the application is still editable. - const application = await db.kYCApplication.findFirst({ - where: { id: applicationId, userId: session.userId }, - select: { id: true, status: true }, - }); - if (!application) { - return NextResponse.json({ error: "Application not found" }, { status: 404 }); - } - if ( - application.status === "approved" || - application.status === "rejected" || - application.status === "expired" - ) { - return NextResponse.json( - { error: "Cannot attach documents to a finalized application" }, - { status: 409 }, - ); - } - - const document = await db.kycDocument.create({ - data: { - applicationId: application.id, - documentType, - fileName, - fileSize: fileSize ?? 0, - mimeType: safeMime, - // Server-controlled storage prefix scoped by application id. - storagePath: `kyc/${application.id}/${Date.now()}-${fileName}`, - status: "pending", - }, - }); - - // Bump the application status forward if it's still in early stages so - // the dashboard reflects upload progress. - if (application.status === "not_started" || application.status === "started") { - await db.kYCApplication.update({ - where: { id: application.id }, - data: { status: "documents_uploaded" }, - }); - } - - await emitAuditLog({ - userId: session.userId, - action: "document_upload", - resource: "kyc_document", - resourceId: document.id, - metadata: { - applicationId: application.id, - documentType, - fileName, - fileSize: fileSize ?? 0, - mimeType: safeMime, + if (!gate.ok) return (gate as { ok: false; response: NextResponse }).response; + + return NextResponse.json( + { + ok: false, + code: "SECURE_DOCUMENT_UPLOAD_NOT_ACTIVE", + error: + "Secure document upload is not active. Do not transmit identity or financial documents through this endpoint.", + requirements: [ + "private object storage", + "short-lived upload authorization", + "file-signature and size validation", + "malware scanning and quarantine", + "reviewer access controls and audit logging", + "retention and deletion enforcement", + ], + }, + { + status: 503, + headers: { + "Cache-Control": "no-store", + "Retry-After": "86400", }, - ipAddress, - userAgent, - }); - - return NextResponse.json({ ok: true, document }); - } catch (error) { - console.error("[POST /api/kyc/documents]", error); - return serverError(); - } + }, + ); } From e9ea641d2d2f49f28d3e171fede86e6ba9c28988 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Thu, 9 Jul 2026 17:07:19 +0000 Subject: [PATCH 08/20] fix(trust): remove unverified live-news and analyst claims --- src/app/intel/news/page.tsx | 325 ++++++------------------------------ 1 file changed, 49 insertions(+), 276 deletions(-) diff --git a/src/app/intel/news/page.tsx b/src/app/intel/news/page.tsx index 15794921..3e68c09d 100644 --- a/src/app/intel/news/page.tsx +++ b/src/app/intel/news/page.tsx @@ -1,30 +1,14 @@ "use client"; -import { useState } from "react"; import Link from "next/link"; -import { - ArrowRight, - ExternalLink, - Maximize2, - Minimize2, - RefreshCw, - Rss, - Shield, - Sparkles, -} from "lucide-react"; +import { AlertTriangle, ArrowRight, ExternalLink, Rss } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { CuratedNewsFeed, type CuratedCategory, } from "@/components/intel/CuratedNewsFeed"; -// Base44-hosted fallback kept while the native feed is warming up. -const GEM_INTEL_FEED_URL = - "https://gem-intel-copy-769950ea.base44.app/news/category/crypto"; - const CATEGORIES: CuratedCategory[] = [ { label: "Crypto & Digital Assets", slug: "crypto" }, { label: "Cybersecurity", slug: "cybersecurity" }, @@ -35,279 +19,68 @@ const CATEGORIES: CuratedCategory[] = [ ]; export default function IntelNewsPage() { - const [expanded, setExpanded] = useState(false); - const [iframeKey, setIframeKey] = useState(0); - const [activeCategory, setActiveCategory] = useState("crypto"); - - const iframeSrc = `https://gem-intel-copy-769950ea.base44.app/news/category/${activeCategory}`; - return (
- {/* ══ HERO ══════════════════════════════════════════════════════════════ */} -
-
-
+
+
+
+
+ +
+
+
-
- - GEM INTEL · LIVE + CURATED - - - - Updating - -
-

- GEM Intel{" "} - News Feed + + SOURCED NEWS PREVIEW + +

+ GEM Intel News

-

- Continuous intelligence across crypto, cybersecurity, markets, - geopolitics, policy, and alternatives — ingested every three - hours and curated by the GEM analyst team. +

+ A category view of externally sourced articles. Feed status, source coverage, + retrieval time, and editorial review must be confirmed before this service is + described as continuous or analyst-curated.

-
- - -
+
- {/* ══ TABS: CURATED (native) + LIVE (iframe) ═══════════════════════════ */} -
- -
- - - - Curated - - - - Live (Base44) - - -
- Cron: every 3h · 17 sources -
-
- - {/* ── Curated (native DB) ─────────────────────────────────────── */} - - - - - {/* ── Live (embedded iframe) ──────────────────────────────────── */} - - {/* Category rail */} -
-
- - Categories - - {CATEGORIES.map((cat) => { - const isActive = cat.slug === activeCategory; - return ( - - ); - })} -
-
- -
-
-
-
- - GEM Intel —{" "} - { - CATEGORIES.find((c) => c.slug === activeCategory)?.label - } - - - Live Feed - -
-
- - - -
-
- -
-