diff --git a/.gitignore b/.gitignore index a547bf3..0727221 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ node_modules dist dist-ssr *.local +playwright-report/ +test-results/ # Editor directories and files .vscode/* diff --git a/e2e/focused-bounty-flow.spec.ts b/e2e/focused-bounty-flow.spec.ts new file mode 100644 index 0000000..cd71e4f --- /dev/null +++ b/e2e/focused-bounty-flow.spec.ts @@ -0,0 +1,80 @@ +import { expect, test } from "@playwright/test"; + +test.describe.configure({ mode: "serial" }); + +test("home explains one authorized security-bounty workflow", async ({ + page, +}) => { + await page.goto("/"); + await expect( + page.getByRole("heading", { name: /test security with scope and proof/i }), + ).toBeVisible(); + await expect( + page.getByRole("region", { name: /from bounty to verified result/i }), + ).toBeVisible(); + await expect( + page.getByRole("region", { + name: /scope is visible. authorization stays separate/i, + }), + ).toBeVisible(); + expect( + await page.evaluate( + () => + document.documentElement.scrollWidth <= + document.documentElement.clientWidth, + ), + ).toBe(true); +}); + +test("mobile home keeps controls out of the content flow", async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto("/"); + await expect( + page.getByRole("heading", { name: /test security/i }), + ).toBeVisible(); + const themeToggle = page.getByRole("button", { + name: /switch to (?:light|dark) mode/i, + }); + await expect(themeToggle).toBeVisible(); + expect( + await themeToggle.evaluate((element) => getComputedStyle(element).position), + ).not.toBe("fixed"); + expect( + await page.evaluate( + () => + document.documentElement.scrollWidth <= + document.documentElement.clientWidth, + ), + ).toBe(true); +}); + +test("discovery is a scannable bounty catalog", async ({ page }) => { + await page.goto("/ideas"); + await expect( + page.getByRole("heading", { name: /authorized security bounties/i }), + ).toBeVisible(); + await expect(page.getByText("27 bounties", { exact: true })).toBeVisible(); + await expect(page.locator("main article")).toHaveCount(27); + await expect(page.getByText("Attack scenario", { exact: true })).toHaveCount( + 0, + ); +}); + +test("bounty detail keeps authorization and proof together", async ({ + page, +}) => { + await page.goto("/ideas/project-time-capsule"); + await expect( + page.getByRole("heading", { + name: "Time Capsule Disclosure Bounty", + exact: true, + }), + ).toBeVisible(); + await expect( + page.getByRole("heading", { name: /scope the test. verify the result/i }), + ).toBeVisible(); + await expect( + page.getByRole("heading", { name: "Rules of engagement", exact: true }), + ).toBeVisible(); + await expect(page.getByText("Proof required", { exact: true })).toBeVisible(); +}); diff --git a/index.html b/index.html index 40e30c4..b5b0de2 100644 --- a/index.html +++ b/index.html @@ -5,9 +5,9 @@ - + - Ideascape — Security validation fieldwork + IdeaScape — Authorized security bounties
diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..2709b32 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,29 @@ +import { defineConfig, devices } from "@playwright/test"; + +const port = 5190; +const baseURL = `http://127.0.0.1:${port}`; + +export default defineConfig({ + testDir: "./e2e", + fullyParallel: false, + forbidOnly: Boolean(process.env.CI), + retries: process.env.CI ? 2 : 0, + reporter: "list", + use: { + baseURL, + trace: "on-first-retry", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + webServer: { + command: + 'bash -lc \'eval "$(supabase status -o env)"; export VITE_SUPABASE_URL="$API_URL" VITE_SUPABASE_PUBLISHABLE_KEY="$PUBLISHABLE_KEY"; npm run dev -- --host 127.0.0.1 --port 5190\'', + url: baseURL, + reuseExistingServer: !process.env.CI, + timeout: 120_000, + }, +}); diff --git a/src/App.test.tsx b/src/App.test.tsx index 06ad69d..4c68029 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -61,30 +61,19 @@ beforeEach(() => { }); describe("App", () => { - it("introduces the Ideascape mission at the home route", () => { + it("introduces the focused security-bounty mission at the home route", () => { renderApp(); const main = screen.getByRole("main"); - const banner = screen.getByRole("banner"); - expect(main).not.toContainElement(banner); - + expect(main).not.toContainElement(screen.getByRole("banner")); expect( screen.getByRole("heading", { - name: /pressure-test security before it ships/i, + name: /test security with scope and proof/i, }), ).toBeInTheDocument(); - expect( - screen.getByText(/security validation lab for early systems/i), - ).toBeInTheDocument(); - expect(screen.getByText("Threats before trust")).toBeInTheDocument(); - expect( - screen.getByText("Threats mapped. Controls bounded."), - ).toBeInTheDocument(); - expect( - screen.getByText(/state what is authorized, excluded/i), - ).toBeInTheDocument(); - expect( - screen.getByText(/precommit tests, stop conditions/i), - ).toBeInTheDocument(); + expect(main).toHaveTextContent( + /system owners publish authorized security bounties/i, + ); + expect(main).toHaveTextContent(/written authorization is always separate/i); expect(screen.getByRole("link", { name: /sign in/i })).toHaveAttribute( "href", "/sign-in", @@ -94,192 +83,89 @@ describe("App", () => { "/sign-up", ); expect( - screen - .getAllByRole("link", { name: /draft a security brief/i }) - .some((link) => link.getAttribute("href") === "/ideas/new"), - ).toBe(true); + screen.getByRole("link", { name: /browse security bounties/i }), + ).toHaveAttribute("href", "/ideas"); expect( - screen - .getAllByRole("link", { name: /review security briefs/i }) - .every((link) => link.getAttribute("href") === "/ideas"), - ).toBe(true); - const explorationNote = screen.getByRole("note", { - name: /security review mode/i, + screen.getByRole("link", { name: /publish a bounty/i }), + ).toHaveAttribute("href", "/ideas/new"); + const rules = screen.getByRole("note", { + name: /authorized bounty rules/i, }); - expect(explorationNote).toHaveTextContent( - /security briefs, not deployment approvals/i, - ); - expect( - screen - .getAllByRole("link", { name: /join the security review/i }) - .every((link) => link.getAttribute("href") === "/sign-up"), - ).toBe(true); - expect(screen.getByText("Security briefs")).toBeInTheDocument(); - expect( - screen.getByText("Security briefs").nextElementSibling, - ).toHaveTextContent("27"); - expect( - screen.getByText( - /every brief names a threat scenario, control boundary, and proof required/i, - ), - ).toBeInTheDocument(); + expect(rules).toHaveTextContent(/no authorization, no test/i); + expect(rules).toHaveTextContent(/does not handle payouts/i); }); - it("hides the join-security-review action from signed-in operators", () => { + it("hides the account-acquisition action from signed-in reviewers", () => { vi.mocked(useAuth).mockReturnValue({ user: { id: "55555555-5555-4555-8555-555555555555", - email: "member@example.com", + email: "reviewer@example.com", } as ReturnType["user"], isLoading: false, }); - renderApp(); - expect( - screen.queryByRole("link", { name: /join the security review/i }), + screen.queryByRole("link", { name: /create an account/i }), ).not.toBeInTheDocument(); }); - it("spotlights concrete security controls", () => { + it("presents one five-step security-bounty workflow", () => { renderApp(); - - expect( - screen.getByRole("link", { name: /browse security domains/i }), - ).toHaveAttribute("href", "#idea-terrain-heading"); - expect( - screen.getByRole("img", { - name: /verifies signed dependencies/i, - }), - ).toBeInTheDocument(); - expect( - screen.getByRole("img", { - name: /contained phishing drill/i, - }), - ).toBeInTheDocument(); - expect( - screen.getByText(/threats mapped. controls bounded/i), - ).toBeInTheDocument(); - expect( - screen.getByText(/software, infrastructure, identity, human-risk/i), - ).toBeInTheDocument(); + const workflow = screen.getByRole("region", { + name: /from bounty to verified result/i, + }); + const steps = within(workflow).getAllByRole("article"); + expect(steps).toHaveLength(5); + for (const [index, title] of [ + "Publish the security bounty", + "Define scope and proof", + "Gather private readiness", + "Run an authorized test", + "Verify and close", + ].entries()) { + expect(steps[index]).toHaveTextContent( + String(index + 1).padStart(2, "0"), + ); + expect( + within(workflow).getByRole("heading", { name: title }), + ).toBeInTheDocument(); + } }); - it("presents six security domains without transaction framing", () => { + it("presents six security areas with the same operating model", () => { renderApp(); - const main = screen.getByRole("main"); expect( - within(main).getByRole("heading", { - name: /security domains under review/i, - }), + within(main).getByRole("heading", { name: /browse by system risk/i }), ).toBeInTheDocument(); - expect( - within(main).getByRole("heading", { name: /ways to challenge a brief/i }), - ).toBeInTheDocument(); - expect( - within(main).getByRole("heading", { name: /proof before scale/i }), - ).toBeInTheDocument(); - for (const category of [ - "Provenance & Authenticity", - "Resilience & Response", - "Human Risk", - "Infrastructure Integrity", + for (const area of [ + "Provenance & Forgery", + "Coordination & Resilience", + "Human Attack Surface", + "Physical & Sensor Systems", "Privacy & Safety", - "Software & Systems", + "Software & Compute", ]) { expect( - within(main).getByRole("heading", { name: category }), + within(main).getByRole("heading", { name: area }), ).toBeInTheDocument(); } - expect(main).toHaveTextContent(/never grants production access/i); expect(main).not.toHaveTextContent( - /smart.contract|crypto wallet|multisig|on.chain|seed phrase|funding rail/i, + /crypto wallet|multisig|on.chain|seed phrase/i, ); }); - it("explains the current idea-validation flow", () => { - renderApp(); - - const howItWorks = screen.getByRole("region", { - name: /the security validation path/i, - }); - const timelineItems = within(howItWorks).getAllByRole("article"); - expect(timelineItems).toHaveLength(10); - expect(timelineItems[0].parentElement).toHaveClass( - "md:grid-cols-2", - "xl:grid-cols-5", - ); - for (const [index, item] of timelineItems.entries()) { - expect(item).toHaveTextContent(String(index + 1).padStart(2, "0")); - expect(item).toHaveClass("md:even:border-r-0"); - expect(item).toHaveClass("xl:[&:nth-child(5n)]:border-r-0"); - } - for (const nextStep of [ - "Frame the system", - "Map the threat scenario", - "Set the control boundary", - "Publish the security brief", - "Collect validation signals", - "Design a bounded pilot", - "Challenge the security case", - "Publish what happened", - "Choose, repeat, or stop", - "Leave a useful record", - ]) { - expect( - within(howItWorks).getByRole("heading", { name: nextStep }), - ).toBeInTheDocument(); - } - expect(howItWorks).toHaveTextContent(/deployment authority/i); - expect( - within(howItWorks).getByRole("link", { - name: /review the security catalog/i, - }), - ).toHaveAttribute("href", "/ideas"); - }); - - it("shows concrete security-review paths without implying authority", () => { + it("makes the platform boundary explicit", () => { renderApp(); - - const participation = screen.getByRole("region", { - name: /ways to challenge a brief/i, + const boundary = screen.getByRole("region", { + name: /scope is visible. authorization stays separate/i, }); - expect(participation).toHaveTextContent(/current security review/i); - expect(participation).toHaveTextContent(/never grants production access/i); - for (const path of [ - "Submit a system", - "Challenge a control", - "Contribute evidence", - ]) { - expect( - within(participation).getByRole("heading", { name: path }), - ).toBeInTheDocument(); - } - }); - - it("explains the evidence and permission questions before expansion", () => { - renderApp(); - - const proof = screen.getByRole("region", { - name: /proof before scale/i, - }); - expect(proof).toHaveTextContent( - /validation signal is not permission to deploy/i, + expect(boundary).toHaveTextContent(/never grants access/i); + expect(boundary).toHaveTextContent( + /payments, escrow, or guaranteed rewards/i, ); - for (const question of [ - "What can fail or be abused?", - "What authority is excluded?", - "How does the control fail safely?", - "What evidence earns trust?", - ]) { - expect( - within(proof).getByRole("heading", { name: question }), - ).toBeInTheDocument(); - } - expect(proof).toHaveTextContent(/nothing advances automatically/i); - expect(proof).toHaveTextContent( - /never grants permission to use private data, property, accounts, or production systems/i, + expect(boundary).toHaveTextContent( + /private readiness signals as aggregate counts/i, ); }); diff --git a/src/App.tsx b/src/App.tsx index babfc3b..fa9a912 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,18 +4,13 @@ import { BookOpen, Cpu, GraduationCap, - HeartHandshake, HeartPulse, Leaf, - Lightbulb, LockKeyhole, Palette, - RefreshCw, ShieldAlert, ShieldCheck, - Sparkles, Users, - Wrench, } from "lucide-react"; import { lazy, Suspense } from "react"; import { Link, Route, Routes } from "react-router-dom"; @@ -66,125 +61,69 @@ const PilotPage = lazy(() => })), ); -const principles = [ +const workflow = [ { - icon: Lightbulb, - title: "Threats made explicit", - description: - "Define the asset, actor, abuse path, and consequence before proposing a control.", - }, - { - icon: Users, - title: "Controls with boundaries", - description: - "State what is authorized, excluded, access-scoped, reversible, and owned.", - }, - { - icon: ShieldCheck, - title: "Proof over confidence", - description: - "Precommit tests, stop conditions, independent checks, and residual risk before trust is earned.", - }, -]; - -const validationSteps = [ - { - icon: Lightbulb, number: "01", - title: "Frame the system", + icon: BookOpen, + title: "Publish the security bounty", description: - "Start with a private security brief that names the system, assets, operators, dependencies, and authority model.", + "The system owner identifies the target, confirms written authorization, and describes the security problem.", }, { - icon: ShieldAlert, number: "02", - title: "Map the threat scenario", + icon: ShieldAlert, + title: "Define scope and proof", description: - "Name who or what could be harmed, how the system could fail or be abused, and which assumptions deserve the hardest questions.", + "The bounty states the attack scenario, rules of engagement, excluded assets, stop conditions, and proof required.", }, { - icon: LockKeyhole, number: "03", - title: "Set the control boundary", + icon: Users, + title: "Gather private readiness", description: - "Define consent, access, data, safety, ownership, rollback, and stop conditions before asking anyone to trust the control.", + "Reviewers can leave private signals. System owners see aggregate evidence, never individual response histories.", }, { - icon: Users, number: "04", - title: "Publish the security brief", + icon: LockKeyhole, + title: "Run an authorized test", description: - "Expose the threat scenario, control boundary, and proof standard for scoped review without granting access or deployment authority.", + "Testing begins only after the target, participants, environment, and stop conditions are explicitly approved.", }, { - icon: ShieldCheck, number: "05", - title: "Collect validation signals", - description: - "Use private reviewer intent and public aggregates to decide whether the security case deserves a bounded exercise.", - }, - { - icon: Wrench, - number: "06", - title: "Design a bounded pilot", - description: - "Define authorized participants, isolated assets, safeguards, measures, rollback, and stop conditions before any live exercise.", - }, - { - icon: ShieldAlert, - number: "07", - title: "Challenge the security case", - description: - "Invite a scoped adversarial review of abuse paths, control failures, recovery steps, and evidence before the pilot earns broader exposure.", - }, - { - icon: BookOpen, - number: "08", - title: "Publish what happened", - description: - "Share outcomes, limits, control failures, and residual risk without exposing reviewer activity or sensitive data.", - }, - { - icon: RefreshCw, - number: "09", - title: "Choose, repeat, or stop", - description: - "Use the evidence to refine one control, repeat inside the same authority boundary, pause, or close the brief without automatic promotion.", - }, - { - icon: Archive, - number: "10", - title: "Leave a useful record", + icon: ShieldCheck, + title: "Verify and close", description: - "Preserve the threat model, controls, evidence, residual risks, decisions, and review date so future work inherits facts instead of confidence theater.", + "A reproducible finding and independent retest determine whether to fix, repeat, pause, or close the bounty.", }, ]; -const ideaTerrains = [ +const securityAreas = [ { icon: Palette, - title: "Provenance & Authenticity", + title: "Provenance & Forgery", description: "Source integrity, consent, authenticity, and controlled reuse.", href: "/ideas?category=arts-culture", }, { icon: Users, - title: "Resilience & Response", + title: "Coordination & Resilience", description: "Private reporting, bounded authority, tested fallback, and incident recovery.", href: "/ideas?category=community", }, { icon: GraduationCap, - title: "Human Risk", + title: "Human Attack Surface", description: "Adversarial training without credential capture, shame, or hidden surveillance.", href: "/ideas?category=education", }, { icon: Leaf, - title: "Infrastructure Integrity", + title: "Physical & Sensor Systems", description: "Fail-safe controls for sensors, utilities, repair, and physical systems.", href: "/ideas?category=environment", @@ -193,69 +132,36 @@ const ideaTerrains = [ icon: HeartPulse, title: "Privacy & Safety", description: - "Privacy-preserving controls for health, accessibility, and environmental safety.", + "Privacy-preserving controls for health, accessibility, and sensitive data.", href: "/ideas?category=health", }, { icon: Cpu, - title: "Software & Systems", + title: "Software & Compute", description: - "Supply chains, devices, recovery, compute, and model operations.", + "Supply chains, devices, recovery, compute isolation, and model operations.", href: "/ideas?category=technology", }, ]; -const participationPaths = [ +const boundaries = [ { - icon: Lightbulb, - title: "Submit a system", - description: - "Draft the assets, trust boundaries, abuse paths, and authority assumptions that need review.", - action: "Draft a security brief", - href: "/ideas/new", + title: "IdeaScape records", + items: [ + "The authorized target and system owner", + "Rules of engagement and stop conditions", + "Private readiness signals as aggregate counts", + "Reproducible findings, remediation, and retest evidence", + ], }, { - icon: HeartHandshake, - title: "Challenge a control", - description: - "Review threat models, identify bypasses, and state whether you can test or operate the control.", - action: "Review security briefs", - href: "/ideas", - }, - { - icon: BookOpen, - title: "Contribute evidence", - description: - "Add standards, incident patterns, reproducible test methods, and explicit stop conditions.", - action: "Join the security review", - href: "/sign-up", - }, -]; - -const proofQuestions = [ - { - number: "01", - title: "What can fail or be abused?", - description: - "Name the asset, actor, entry point, trust violation, and credible consequence.", - }, - { - number: "02", - title: "What authority is excluded?", - description: - "Make production access, data collection, custody, payment, and deployment authority explicit.", - }, - { - number: "03", - title: "How does the control fail safely?", - description: - "Define isolation, least privilege, rollback, recovery, and the condition that stops the exercise.", - }, - { - number: "04", - title: "What evidence earns trust?", - description: - "Precommit reproducible checks and residual-risk criteria before a control can advance.", + title: "IdeaScape does not provide", + items: [ + "Permission to access any target", + "Production credentials or private data", + "Payments, escrow, or guaranteed rewards", + "Automatic approval to deploy or expand a test", + ], }, ]; @@ -276,34 +182,31 @@ function HomePage() { showAction={!isAuthLoading && !user} /> -
+

- Threats before trust + Authorized security bounty platform

-

- Pressure-test security{" "} - before it ships. +

+ Test security with{" "} + scope and proof.

- Ideascape is a security validation lab for early systems. - Operators publish a concrete threat scenario, control boundary, - and proof standard before any pilot earns trust. + IdeaScape helps system owners publish authorized security + bounties and helps reviewers evaluate whether each one is ready + for a controlled test.

- Review software, infrastructure, identity, human-risk, privacy, - and provenance briefs built to expose failure paths—not collect - applause. + Every bounty defines the attack scenario, rules of engagement, + and proof required. Written authorization is always separate + from the listing itself.

- Draft a security brief + Browse security bounties
- Security briefs + Published bounties
27 @@ -340,7 +232,7 @@ function HomePage() {
- Security domains + Security areas
6 @@ -348,84 +240,111 @@ function HomePage() {
- Method + Non-negotiable
- Threat. Control. Proof. + No authorization, no test.
-
-
+
- Field sample / 027 - Permission checked + Security bounty / 027 + + Authorization required +
A software maintainer verifies signed dependencies in an isolated build environment
-
- - Software Supply Chain Clinic - - - Software & Systems - +
+

+ Dependency Substitution Bounty +

+

+ Isolated build environment · reproducible retest required +

-
-
- An operator reviews a contained phishing drill with credential capture disabled +
+
+ +
+
+
+
+

One workflow

+

+ From bounty to verified result +

+

+ The same five steps apply across every security area. +

+ + View current bounties +
-
-
-
+
-

Six attack surfaces

+

Six security areas

- Security domains under review + Browse by system risk

- Ideascape covers digital and physical systems where weak - authority boundaries, unverifiable claims, or unsafe failure - modes can create real harm. + Each area uses the same authorization, scope, privacy, and + evidence requirements.

-
-

- 27 security briefs across 6 domains -

-

- Every brief names a threat scenario, control boundary, and - proof required before a larger test. -

-
- {ideaTerrains.map( + {securityAreas.map( ({ icon: Icon, title, description, href }) => (
@@ -452,231 +371,58 @@ function HomePage() {
-
-
-
-
-

From threat to evidence

-

- The security validation path -

-

- Start with a credible threat, not a confidence claim. Every - control advances through explicit authority and proof gates. -

-
- - Review the security catalog -
-
- {validationSteps.map( - ({ icon: Icon, number, title, description }) => ( -
-
- - -
- - {number} - -
-
-

- {title} -

-

- {description} -

-
- ), - )} -
-
-
- -
+
-
-
-
-
-

- Ways to challenge a brief -

-

- Draft a threat model, challenge a control boundary, or add - reproducible evidence. Review never grants production - access, deployment authority, payment, or custody. -

-
- -
-
- {participationPaths.map( - ({ icon: Icon, title, description, action, href }) => ( -
- - -

{title}

-

- {description} -

- {href === "/sign-up" && user ? ( - - You're reviewing - - ) : ( - - {action} -
- ), - )} -
-
-
- -
-
-
-
-

-

-

- Proof before scale -

-

- A validation signal is not permission to deploy. Every - security brief must tighten its evidence, authority - boundary, recovery path, and stop conditions as it advances. -

-
- +

+

+

- See the questions in practice -

+

+ A listing helps people evaluate a test. It never grants access + to a system or permission to begin testing. +

-
- {proofQuestions.map(({ number, title, description }) => ( -
+ {boundaries.map(({ title, items }, index) => ( +
-
- - Question {number} - -
+
    + {items.map((item) => ( +
  • +
  • + ))} +
+
))} -
- - -

- A security brief can stop, change direction, or remain a - useful review record. Nothing advances automatically, and - validation never grants permission to use private data, - property, accounts, or production systems. -

-
- -
- -
-
-
-

- Security operating principles -

-

- From threat model to trusted control -

-
-
-
- {principles.map(({ icon: Icon, title, description }, index) => ( -
-
- - - - 0{index + 1} - -
-

{title}

-

- {description} -

-
- ))}
@@ -715,7 +461,7 @@ function RouteLoadingFallback() { return (

Loading page… diff --git a/src/bounty-network-positioning.test.ts b/src/bounty-network-positioning.test.ts new file mode 100644 index 0000000..9f61f49 --- /dev/null +++ b/src/bounty-network-positioning.test.ts @@ -0,0 +1,274 @@ +import { existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; + +const repositoryRoot = join(import.meta.dirname, ".."); +const read = (path: string) => readFileSync(join(repositoryRoot, path), "utf8"); + +const bountyMigration = + "supabase/migrations/20260811193000_recast_as_authorized_bounty_network.sql"; + +const productModel = { + primaryObject: "security bounty", + owner: "system owner", + reviewer: "reviewer", + execution: "authorized test run", +} as const; + +const majorSurfaces = [ + "src/App.tsx", + "src/components/interest-mode-notice.tsx", + "src/components/site-header.tsx", + "src/features/auth/auth-page.tsx", + "src/features/ideas/idea-discovery-page.tsx", + "src/features/ideas/idea-detail-page.tsx", + "src/features/ideas/idea-editor-page.tsx", + "src/features/ideas/idea-interest-panel.tsx", + "src/features/ideas/idea-validation-panel.tsx", + "src/features/ideas/idea-validation-evidence-panel.tsx", + "src/features/profiles/profile-page.tsx", + "src/features/pilots/pilot-page.tsx", + "src/features/admin/admin-page.tsx", +]; + +const bountyCatalog = [ + [ + "00000000-0000-4000-8000-000000000201", + "clean-air-library", + "Smoke Sensor Spoofing Bounty", + ], + [ + "00000000-0000-4000-8000-000000000202", + "repair-commons", + "Repair Station Privilege Bounty", + ], + [ + "00000000-0000-4000-8000-000000000203", + "neighbor-ride-credits", + "Trip Relay Metadata Bounty", + ], + [ + "00000000-0000-4000-8000-000000000204", + "after-dark-storefronts", + "Night Install Tamper Bounty", + ], + [ + "00000000-0000-4000-8000-000000000205", + "shade-stop-network", + "Transit Sensor Blind-Spot Bounty", + ], + [ + "00000000-0000-4000-8000-000000000206", + "skill-swap-saturdays", + "Repair Playbook Injection Bounty", + ], + [ + "00000000-0000-4000-8000-000000000207", + "civic-accessibility-lab", + "Crossing Signal Failure Bounty", + ], + [ + "00000000-0000-4000-8000-000000000208", + "block-ready-kits", + "Outage Kit Supply-Chain Bounty", + ], + [ + "00000000-0000-4000-8000-000000000209", + "device-liberation-lab", + "Device Unlock Boundary Bounty", + ], + [ + "00000000-0000-4000-8000-000000000210", + "file-rescue-cooperative", + "File Recovery Integrity Bounty", + ], + [ + "00000000-0000-4000-8000-000000000211", + "cloud-exit-toolkit", + "Cloud Exit Data-Loss Bounty", + ], + [ + "00000000-0000-4000-8000-000000000212", + "private-ai-workbench", + "Local AI Data-Leak Bounty", + ], + [ + "00000000-0000-4000-8000-000000000213", + "home-lab-defense-clinic", + "Home Lab Exposure Bounty", + ], + [ + "00000000-0000-4000-8000-000000000214", + "community-compute-cooperative", + "Shared Compute Escape Bounty", + ], + [ + "00000000-0000-4000-8000-000000000215", + "offline-mesh-field-kit", + "Mesh Relay Spoofing Bounty", + ], + [ + "00000000-0000-4000-8000-000000000216", + "open-repair-atlas", + "Repair Atlas Poisoning Bounty", + ], + [ + "00000000-0000-4000-8000-000000000217", + "accessible-interface-retrofit-lab", + "Accessible UI Regression Bounty", + ], + [ + "00000000-0000-4000-8000-000000000218", + "project-time-capsule", + "Time Capsule Disclosure Bounty", + ], + [ + "00000000-0000-4000-8000-000000000219", + "waste-heat-works", + "Heat Controller Fail-Safe Bounty", + ], + [ + "00000000-0000-4000-8000-000000000220", + "model-commons-lab", + "Model Eval Poisoning Bounty", + ], + [ + "00000000-0000-4000-8000-000000000221", + "glass-box-sensor-network", + "Plate Reader Privacy Bounty", + ], + [ + "00000000-0000-4000-8000-000000000222", + "oral-history-provenance-lab", + "Oral History Provenance Bounty", + ], + [ + "00000000-0000-4000-8000-000000000223", + "neighborhood-incident-relay", + "Incident Relay Impersonation Bounty", + ], + [ + "00000000-0000-4000-8000-000000000224", + "phishing-drill-library", + "Phishing Drill Containment Bounty", + ], + [ + "00000000-0000-4000-8000-000000000225", + "water-sensor-integrity-watch", + "Water Sensor Spoofing Bounty", + ], + [ + "00000000-0000-4000-8000-000000000226", + "clinic-device-privacy-check", + "Clinic Device Privacy Bounty", + ], + [ + "00000000-0000-4000-8000-000000000227", + "software-supply-chain-clinic", + "Dependency Substitution Bounty", + ], +] as const; + +describe("focused authorized security bounty platform", () => { + it("uses one focused product model instead of competing hacker metaphors", () => { + const visibleCopy = majorSurfaces.map(read).join("\n"); + for (const term of Object.values(productModel)) { + expect(visibleCopy).toMatch(new RegExp(term, "i")); + } + for (const distractingMetaphor of [ + /channel open/i, + /dead drop/i, + /decrypt(?:ing)?/i, + /swagger/i, + /wreckage/i, + /kill chain/i, + /clean kill/i, + /\bhunters?\b/i, + /\bdossiers?\b/i, + /\breceipts?\b/i, + /\bproof-gates?\b/i, + /\bbounty board\b/i, + /\battack sectors?\b/i, + /\btarget intel\b/i, + /\b(?:clear|clean) trace\b/i, + /\bbounty killed\b/i, + ]) { + expect(visibleCopy).not.toMatch(distractingMetaphor); + } + expect(visibleCopy).not.toMatch(/\bSkull\b/); + expect(read("src/components/interest-mode-notice.tsx")).not.toMatch( + /\bCrosshair\b/, + ); + expect(read("src/components/site-header.tsx")).toContain("IdeaScape"); + expect(read("src/components/site-header.tsx")).not.toContain(">Ideascape<"); + }); + + it("uses the same security-bounty vocabulary on every major surface", () => { + for (const path of majorSurfaces) { + expect(read(path), `${path} lacks focused product language`).toMatch( + /\b(?:security bount(?:y|ies)|system owner|reviewer|authorized test run|rules of engagement)\b/i, + ); + } + }); + + it("makes authorization the non-negotiable rule without claiming platform payouts", () => { + const visibleCopy = majorSurfaces.map(read).join("\n"); + expect(visibleCopy).toMatch(/no authorization, no test/i); + expect(visibleCopy).toMatch(/written permission/i); + expect(visibleCopy).toMatch(/does not handle payouts/i); + expect(visibleCopy).not.toMatch( + /\b(?:hack anything|no rules|unauthorized targets?|guaranteed payouts?|instant cash)\b/i, + ); + }); + + it("keeps the original private-interest meanings instead of turning them into hacker roles", () => { + const interestPanel = read("src/features/ideas/idea-interest-panel.tsx"); + for (const label of [ + "I would use this", + "I would help build it", + "I could join an authorized test run", + "I have relevant expertise", + "Keep me updated", + ]) { + expect(interestPanel).toContain(label); + } + expect(interestPanel).toMatch(/private interest/i); + expect(interestPanel).toMatch(/grants no access/i); + expect(interestPanel).toContain('value: "pilot"'); + }); + + it("uses a black, bone, and signal-orange code-brutalist system", () => { + const css = read("src/index.css"); + expect(css).toContain("--background: #050505"); + expect(css).toContain("--foreground: #f2efe6"); + expect(css).toContain("--signal: #ff5a1f"); + expect(css).toMatch(/\.packet-trace/); + expect(css).toMatch(/\.bounty-grid/); + }); + + it("rewrites all 27 deterministic examples as distinct authorized bounties", () => { + expect(existsSync(join(repositoryRoot, bountyMigration))).toBe(true); + const migration = read(bountyMigration); + for (const [id, slug, title] of bountyCatalog) { + expect(migration).toContain(id); + expect(migration).toContain(slug); + expect(migration).toContain(title); + } + expect(migration.match(/ Bounty'/g)).toHaveLength(27); + expect(migration).toMatch( + /requires all 27 expected bounty UUID\/slug pairs/i, + ); + expect(migration).toMatch( + /authorized (?:sandbox|environment|assets?|systems?)/i, + ); + expect(migration).toMatch(/rules of engagement/i); + expect(migration).toMatch(/proof_required/i); + expect(migration).not.toContain("ready-in-range"); + expect(migration).not.toContain( + "Authorized security bounty illustration for ", + ); + expect(migration).toContain( + "Time Capsule Disclosure Bounty authorized test run", + ); + }); +}); diff --git a/src/components/interest-mode-notice.tsx b/src/components/interest-mode-notice.tsx index d9d40e1..c854de0 100644 --- a/src/components/interest-mode-notice.tsx +++ b/src/components/interest-mode-notice.tsx @@ -1,4 +1,4 @@ -import { ArrowRight, FlaskConical } from "lucide-react"; +import { ArrowRight, FileCheck2 } from "lucide-react"; import { Link } from "react-router-dom"; import { buttonVariants } from "@/components/ui/button"; import { cn } from "@/lib/utils"; @@ -14,7 +14,7 @@ export function InterestModeNotice({ }: InterestModeNoticeProps) { return (

-
-

Security review mode

+

Rules of engagement

- These are security briefs, not deployment approvals. Every brief - states a threat scenario, control boundary, and proof standard. No - payment, production access, or operational authority changes - hands. + These are authorized security bounties, not invitations to probe + random systems. Test only assets you own or have written + permission to test. IdeaScape does not handle payouts, grant + production access, or transfer operational authority. No + authorization, no test.

@@ -44,7 +45,7 @@ export function InterestModeNotice({ })} to="/sign-up" > - Join the security review + Create an account
) : null} @@ -201,17 +202,17 @@ export function IdeaDetailPage() {
) : null} @@ -237,7 +238,7 @@ export function IdeaDetailPage() { {ideaQuery.data.summary}

- Brief owner{" "} + System owner{" "} 0 ? (

{ideaQuery.data.media.map((media) => ( @@ -271,7 +272,7 @@ export function IdeaDetailPage() { className="text-2xl font-semibold tracking-tight" id="about-idea" > - System description + Bounty scope

@@ -296,22 +297,22 @@ export function IdeaDetailPage() {

- Evidence before commitment + Authorized test run

- See the precommitted pilot rules + Review the test-run plan

- Review the evidence window, capacity, safety boundaries, - and continue, revise, or archive thresholds before intake - opens. + Review the evidence window, authorized assets, stop + conditions, and continue, revise, or close thresholds + before testing begins.

- View pilot plan + View test-run plan
@@ -325,13 +326,13 @@ export function IdeaDetailPage() {

- Continue the review + Related security bounties

- Review all {ideaQuery.data.category.name} briefs + View all {ideaQuery.data.category.name} bounties
-
+
{relatedIdeas.map((relatedIdea) => { const cover = relatedIdea.media.find( (media) => diff --git a/src/features/ideas/idea-discovery-page.test.tsx b/src/features/ideas/idea-discovery-page.test.tsx index 0ef62a7..f62566b 100644 --- a/src/features/ideas/idea-discovery-page.test.tsx +++ b/src/features/ideas/idea-discovery-page.test.tsx @@ -85,7 +85,7 @@ describe("IdeaDiscoveryPage", () => { renderDiscovery(); expect(screen.getByRole("status")).toHaveTextContent( - /loading security briefs/i, + /loading security bounties/i, ); }); @@ -95,7 +95,9 @@ describe("IdeaDiscoveryPage", () => { renderDiscovery(); expect( - await screen.findByRole("heading", { name: /review security briefs/i }), + await screen.findByRole("heading", { + name: /authorized security bounties/i, + }), ).toBeInTheDocument(); expect( screen.queryByRole("link", { name: /^explore ideas$/i }), @@ -108,14 +110,14 @@ describe("IdeaDiscoveryPage", () => { expect(screen.getAllByText("Software & Systems").length).toBeGreaterThan(0); expect( screen.getByText( - /every brief names a threat scenario, control boundary, and proof required/i, + /every bounty names the attack scenario, rules of engagement, and proof required/i, ), ).toBeInTheDocument(); - expect(screen.getByText("Security brief")).toBeInTheDocument(); - expect(screen.getByText("Security focus")).toBeInTheDocument(); - expect(screen.getByText(idea.threat_scenario)).toBeInTheDocument(); - expect(screen.getByText("1 security brief")).toBeInTheDocument(); - expect(screen.getByText("4 validation signals")).toBeInTheDocument(); + expect(screen.getByText("Bounty open")).toBeInTheDocument(); + expect(screen.queryByText("Attack scenario")).not.toBeInTheDocument(); + expect(screen.queryByText(idea.threat_scenario)).not.toBeInTheDocument(); + expect(screen.getByText("1 bounty")).toBeInTheDocument(); + expect(screen.getByText("4 readiness signals")).toBeInTheDocument(); expect( screen.getByRole("img", { name: /solar desalination prototype/i }), ).toHaveAttribute("src", idea.media[0].url); @@ -124,8 +126,8 @@ describe("IdeaDiscoveryPage", () => { `/profiles/${idea.creator.username}`, ); expect( - screen.getByRole("note", { name: /security review mode/i }), - ).toHaveTextContent(/security briefs, not deployment approvals/i); + screen.getByRole("note", { name: /authorized bounty rules/i }), + ).toHaveTextContent(/does not handle payouts/i); }); it("does not claim a security case for concepts without all three fields", async () => { @@ -143,7 +145,8 @@ describe("IdeaDiscoveryPage", () => { expect( await screen.findByRole("link", { name: `View ${idea.title}` }), ).toBeInTheDocument(); - expect(screen.queryByText("Security focus")).not.toBeInTheDocument(); + expect(screen.queryByText("Attack scenario")).not.toBeInTheDocument(); + expect(screen.queryByText(idea.threat_scenario)).not.toBeInTheDocument(); }); it("restores a category filter from the URL and only shows matching concepts", async () => { @@ -152,7 +155,7 @@ describe("IdeaDiscoveryPage", () => { renderDiscovery("/ideas?category=technology"); expect( - await screen.findByRole("combobox", { name: /security domain/i }), + await screen.findByRole("combobox", { name: /security area/i }), ).toHaveValue("technology"); expect( screen.getByRole("link", { name: `View ${idea.title}` }), @@ -160,9 +163,7 @@ describe("IdeaDiscoveryPage", () => { expect( screen.queryByRole("link", { name: `View ${healthIdea.title}` }), ).not.toBeInTheDocument(); - expect( - screen.getByText("Showing 1 of 2 security briefs"), - ).toBeInTheDocument(); + expect(screen.getByText("Showing 1 of 2 bounties")).toBeInTheDocument(); }); it("restores search from the URL and offers a clear path when no concepts match", async () => { @@ -172,7 +173,7 @@ describe("IdeaDiscoveryPage", () => { renderDiscovery("/ideas?q=heat"); const search = await screen.findByRole("searchbox", { - name: /search security briefs/i, + name: /search security bounties/i, }); expect(search).toHaveValue("heat"); expect( @@ -187,7 +188,7 @@ describe("IdeaDiscoveryPage", () => { expect( screen.getByRole("heading", { - name: /no security briefs match these filters/i, + name: /no security bounties match these filters/i, }), ).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: /clear filters/i })); @@ -201,7 +202,7 @@ describe("IdeaDiscoveryPage", () => { renderDiscovery(); expect( - await screen.findByText("Be first to add a validation signal"), + await screen.findByText("Be first to leave a readiness signal"), ).toBeInTheDocument(); expect(screen.queryByText(/fund now/i)).not.toBeInTheDocument(); }); @@ -213,11 +214,11 @@ describe("IdeaDiscoveryPage", () => { expect( await screen.findByRole("heading", { - name: /the first security briefs are taking shape/i, + name: /no security bounties yet/i, }), ).toBeInTheDocument(); const startIdeaLinks = screen.getAllByRole("link", { - name: /draft a security brief/i, + name: /publish a bounty/i, }); expect(startIdeaLinks).toHaveLength(2); for (const link of startIdeaLinks) { @@ -233,9 +234,9 @@ describe("IdeaDiscoveryPage", () => { renderDiscovery(); expect(await screen.findByRole("alert")).toHaveTextContent( - "Unable to load security briefs. Please try again.", + "Unable to load security bounties. Try again.", ); - expect(screen.getByText("Catalog unavailable")).toBeInTheDocument(); + expect(screen.getByText("Listings unavailable")).toBeInTheDocument(); expect(screen.getByRole("alert")).not.toHaveTextContent(/sensitive/i); }); @@ -249,12 +250,12 @@ describe("IdeaDiscoveryPage", () => { }); expect(await screen.findByRole("alert")).toHaveTextContent( - "Unable to refresh security briefs. Showing the latest available catalog.", + "Unable to refresh security bounties. Showing the latest available listings.", ); - expect(screen.getByText("1 security brief")).toBeInTheDocument(); + expect(screen.getByText("1 bounty")).toBeInTheDocument(); expect( screen.getByRole("link", { name: `View ${idea.title}` }), ).toBeInTheDocument(); - expect(screen.queryByText("Catalog unavailable")).not.toBeInTheDocument(); + expect(screen.queryByText("Listings unavailable")).not.toBeInTheDocument(); }); }); diff --git a/src/features/ideas/idea-discovery-page.tsx b/src/features/ideas/idea-discovery-page.tsx index 3362a6b..4062965 100644 --- a/src/features/ideas/idea-discovery-page.tsx +++ b/src/features/ideas/idea-discovery-page.tsx @@ -5,7 +5,6 @@ import { ArrowUpRight, LoaderCircle, Search, - ShieldCheck, Sparkles, UsersRound, } from "lucide-react"; @@ -19,12 +18,12 @@ import { } from "@/features/ideas/idea-discovery-service"; const statusLabels: Record = { - published: "Security brief", - funding: "Control review", - funded: "Pilot approved", - in_progress: "Exercise active", - completed: "Evidence published", - cancelled: "Closed", + published: "Bounty open", + funding: "Rules under review", + funded: "Test run approved", + in_progress: "Authorized test active", + completed: "Results published", + cancelled: "Bounty closed", }; function isSafeImageUrl(value: string): boolean { @@ -38,18 +37,18 @@ function isSafeImageUrl(value: string): boolean { function interestLabel(count: number): string { if (count === 0) { - return "Be first to add a validation signal"; + return "Be first to leave a readiness signal"; } if (count === 1) { - return "1 validation signal"; + return "1 readiness signal"; } - return `${count} validation signals`; + return `${count} readiness signals`; } -function conceptCountLabel(count: number): string { - return `${count} security ${count === 1 ? "brief" : "briefs"}`; +function bountyCountLabel(count: number): string { + return `${count} ${count === 1 ? "bounty" : "bounties"}`; } export function IdeaDiscoveryPage() { @@ -133,28 +132,29 @@ export function IdeaDiscoveryPage() {

- Security validation catalog + Security bounties · public listings

- Review security briefs + Authorized{" "} + security bounties

- Every brief names a threat scenario, control boundary, and - proof required before it can advance to a bounded exercise. + Every bounty names the attack scenario, rules of engagement, + and proof required before an authorized test can begin.

{ideasQuery.isError && ideasQuery.data === undefined ? "Catalog status" - : "Under review"} + : "Listing status"}

{ideasQuery.isError && ideasQuery.data === undefined - ? "Catalog unavailable" + ? "Listings unavailable" : ideasQuery.data - ? conceptCountLabel(ideasQuery.data.length) - : "Loading security briefs"} + ? bountyCountLabel(ideasQuery.data.length) + : "Loading security bounties"}

@@ -162,12 +162,12 @@ export function IdeaDiscoveryPage() { {ideasQuery.data && ideasQuery.data.length > 0 ? (
- Filter security briefs + Filter authorized bounties