diff --git a/apps/web/src/app/api/contact/route.ts b/apps/web/src/app/api/contact/route.ts
index 546f4c0..0b993f6 100644
--- a/apps/web/src/app/api/contact/route.ts
+++ b/apps/web/src/app/api/contact/route.ts
@@ -13,11 +13,35 @@ function getSupabase() {
return supabase;
}
+// Labels for the extra fields a form may submit (the hire form sends these).
+// Used both in the notification email and in the persisted message.
+const FIELD_LABELS: Record = {
+ email: "Valid email",
+ target: "App or repo",
+ stack: "Stack",
+ timeline: "Timeline",
+};
+
+// contact_requests only has columns for company and topic, so any other extra
+// field would be lost on the way to the database. Append them to the stored
+// message instead of dropping them.
+function messageWithExtras(s: {
+ message: string;
+ fields: Record;
+}): string {
+ const extras = Object.entries(s.fields).filter(
+ ([key, value]) => key !== "company" && key !== "topic" && value,
+ );
+ if (extras.length === 0) return s.message;
+ const lines = extras.map(([key, value]) => `${FIELD_LABELS[key] ?? key}: ${value}`);
+ return `${s.message}\n\n---\n${lines.join("\n")}`;
+}
+
export const POST = createContactRoute({
from: "ThreatCrush ",
to: "hello@threatcrush.com",
honeypot: false,
- fieldLabels: { email: "Valid email" },
+ fieldLabels: FIELD_LABELS,
subject: (s) =>
`[ThreatCrush] New ${s.fields.topic ?? "general"} inquiry from ${s.name}`,
onSendError: "ignore",
@@ -28,7 +52,7 @@ export const POST = createContactRoute({
name: s.name,
email: s.email.toLowerCase(),
company: s.fields.company ?? null,
- message: s.message,
+ message: messageWithExtras(s),
topic: s.fields.topic ?? "general",
})
.select("id")
diff --git a/apps/web/src/app/hire/page.tsx b/apps/web/src/app/hire/page.tsx
new file mode 100644
index 0000000..101bf9f
--- /dev/null
+++ b/apps/web/src/app/hire/page.tsx
@@ -0,0 +1,191 @@
+import type { Metadata } from "next";
+import { SITE_URL } from "@/lib/blog";
+import { HireForm } from "@/components/HireForm";
+
+export const metadata: Metadata = {
+ title: "Hire Us — human-led security assessments",
+ description:
+ "Have our team run the scan for you and read the results by hand. Engagements start at $400 for a scan with human input and scale with the complexity of your application.",
+ alternates: { canonical: "/hire" },
+ openGraph: {
+ title: "Hire Us · ThreatCrush",
+ description:
+ "Human-led security assessments built on the ThreatCrush engine. Starts at $400 for a scan with human input.",
+ url: `${SITE_URL}/hire`,
+ type: "website",
+ },
+};
+
+const serviceJsonLd = {
+ "@context": "https://schema.org",
+ "@type": "Service",
+ name: "ThreatCrush human-led security assessment",
+ serviceType: "Application security assessment",
+ url: `${SITE_URL}/hire`,
+ provider: { "@type": "Organization", name: "ThreatCrush", url: SITE_URL },
+ areaServed: "Worldwide",
+ offers: {
+ "@type": "Offer",
+ priceCurrency: "USD",
+ availability: "https://schema.org/InStock",
+ url: `${SITE_URL}/hire`,
+ priceSpecification: {
+ "@type": "PriceSpecification",
+ priceCurrency: "USD",
+ minPrice: 400,
+ },
+ },
+};
+
+const breadcrumbJsonLd = {
+ "@context": "https://schema.org",
+ "@type": "BreadcrumbList",
+ itemListElement: [
+ { "@type": "ListItem", position: 1, name: "Home", item: `${SITE_URL}/` },
+ { "@type": "ListItem", position: 2, name: "Hire Us", item: `${SITE_URL}/hire` },
+ ],
+};
+
+const deliverables = [
+ {
+ title: "A scan you didn't have to run",
+ body: "We point the ThreatCrush engine at your repositories and infrastructure, tune the rules to your stack, and re-run until the picture is complete.",
+ },
+ {
+ title: "Findings read by a human",
+ body: "Every finding is triaged by an engineer before you see it. False positives get dropped; real issues arrive with severity, reproduction steps, and a fix.",
+ },
+ {
+ title: "A report you can hand to anyone",
+ body: "One document for your engineers and one summary for whoever asked — customer, auditor, or board — mapped to MITRE ATT&CK and NIST CSF.",
+ },
+ {
+ title: "A working session at the end",
+ body: "We walk the findings with your team, answer questions, and agree what gets fixed first. A re-scan after your fixes land is part of the engagement.",
+ },
+];
+
+const steps = [
+ {
+ n: "01",
+ title: "Tell us about the app",
+ body: "Stack, size, where it runs, and what you are worried about. A couple of minutes on the form below.",
+ },
+ {
+ n: "02",
+ title: "We scope it and send a number",
+ body: "You get a fixed price and a timeline in writing before anything starts. No hourly surprises.",
+ },
+ {
+ n: "03",
+ title: "We scan, triage, and report",
+ body: "Automated coverage first, then human review of everything it surfaced — plus the things a scanner cannot see.",
+ },
+ {
+ n: "04",
+ title: "You fix, we verify",
+ body: "We re-run the assessment against your fixes so the close-out report shows the delta, not just the starting point.",
+ },
+];
+
+export default function HirePage() {
+ return (
+
+
+
+
+
+
// HIRE US
+
+ Let us run it,{" "}
+ and read the results.
+
+
+ ThreatCrush is open source and you can run it yourself. When you would rather
+ hand the whole thing to someone, our team scans your application, triages every
+ finding by hand, and gives you a report that says what is actually broken and
+ what to do about it.
+
+
+ {/* No public rate card, but the floor is stated plainly so nobody has to book a
+ call just to find out whether they can afford us. */}
+
+
+
+
What it costs
+
+ We don't quote prices up front, because no two applications are the
+ same amount of work. What we can tell you is where it starts:{" "}
+
+ engagements begin at $400 for a scan with human input
+ {" "}
+ — the engine runs, and an engineer reads and triages what it found.
+
+
+ From there the price moves with the complexity of the app: how many
+ services and repositories are in scope, what it is written in, whether
+ there is infrastructure and cloud configuration to review, and how much
+ manual testing the thing warrants. Tell us about it below and we come back
+ with a fixed number and a timeline before any work begins.
+
+
+ Prefer to self-serve? The{" "}
+
+ docs
+ {" "}
+ will get you scanning in a few minutes, and the{" "}
+
+ module store
+ {" "}
+ covers what the agent can do.
+
+
+
+
+
+
What you get
+
+ {deliverables.map((d) => (
+
+
{d.title}
+
{d.body}
+
+ ))}
+
+
+
+
+
How it runs
+
+ {steps.map((s) => (
+
+ {s.n}
+
+
{s.title}
+
{s.body}
+
+
+ ))}
+
+
+
+
+
Tell us about your app
+
+ A few details is enough to scope it. We reply with a price and a timeline,
+ usually the same business day.
+