jackblatch/OneStopShop

12 errors, 9 warningsmain

6 critical findings to fix first.

12 errors and 9 warnings in the paths between 21 routes and 6 tables.

Every file was read, but 1 place in them could not be parsed — listed at the end of this report.

ROUTE FINDINGS9 of 21 · 12/12 rules
ROUTES
21
TABLES
6
FILES READ
160/160
RULES RUN
12/12

21 findings

6 critical6 high9 medium

  • Criticalmedium confidenceServer Action addToCart (server-actions/add-to-cart.ts) can reach carts without authenticating.

    Anyone, signed in or not, can write to carts (client_secret) — personal or secret fields.

    Data reached
    carts (write) · sensitive: client_secret
    Missing
    Authentication before the handler reaches data, and a query scoped to the caller.
    Confidence
    middleware.ts exists. A Server Action is POSTed to the page that uses it, so if the matcher covers that page and rejects signed-out requests, this is a false positive.
    Path
    Server Action addToCart (server-actions/add-to-cart.ts) → carts

    Prove it

    # A Server Action is a public POST endpoint. Its id ships to the browser:
    # find it in your built client chunks next to "addToCart".
    curl -i -X POST 'https://YOUR_APP/' \
      -H 'Next-Action: ACTION_ID' \
      -H 'content-type: text/plain;charset=UTF-8' -d '[]'
    # No cookie is sent. A 200 that runs the action means anyone can call addToCart.

    Fix

    "use server";
    
    export async function addToCart(formData: FormData) {
      // The page's own session check does not run for an action: check here.
      const user = await getCurrentUser();   // e.g. (await supabase.auth.getUser()).data.user
      if (!user) throw new Error("Unauthorized");
      // ...scope every query to user.id
    }

    Regression test

    import { addToCart } from "@/server-actions/add-to-cart";
    
    // Make your session lookup return no user for this test.
    vi.mock("@/lib/auth", () => ({ getCurrentUser: async () => null }));
    
    test("addToCart refuses a signed-out caller", async () => {
      await expect(addToCart(new FormData())).rejects.toThrow();
    });

    Intended? This cannot be declared intentional: a public write is not a design choice the check will bless. Accept it as known debt: run wyro-check --update-baseline and commit the ledger. It stays visible and CI fails only on new findings.

    auth-before-data

  • Criticalmedium confidenceServer Action getCart (server-actions/get-cart-details.ts) can reach carts without authenticating.

    Anyone, signed in or not, can read carts (client_secret) — personal or secret fields.

    Data reached
    carts (read) · sensitive: client_secret
    Missing
    Authentication before the handler reaches data, and a query scoped to the caller.
    Confidence
    middleware.ts exists. A Server Action is POSTed to the page that uses it, so if the matcher covers that page and rejects signed-out requests, this is a false positive.
    Path
    Server Action getCart (server-actions/get-cart-details.ts) → carts

    Prove it

    # A Server Action is a public POST endpoint. Its id ships to the browser:
    # find it in your built client chunks next to "getCart".
    curl -i -X POST 'https://YOUR_APP/' \
      -H 'Next-Action: ACTION_ID' \
      -H 'content-type: text/plain;charset=UTF-8' -d '[]'
    # No cookie is sent. A 200 that runs the action means anyone can call getCart.

    Fix

    "use server";
    
    export async function getCart(formData: FormData) {
      // The page's own session check does not run for an action: check here.
      const user = await getCurrentUser();   // e.g. (await supabase.auth.getUser()).data.user
      if (!user) throw new Error("Unauthorized");
      // ...scope every query to user.id
    }

    Regression test

    import { getCart } from "@/server-actions/get-cart-details";
    
    // Make your session lookup return no user for this test.
    vi.mock("@/lib/auth", () => ({ getCurrentUser: async () => null }));
    
    test("getCart refuses a signed-out caller", async () => {
      await expect(getCart(new FormData())).rejects.toThrow();
    });

    Intended? This cannot be declared intentional: public sensitive data is not a design choice the check will bless. Accept it as known debt: run wyro-check --update-baseline and commit the ledger. It stays visible and CI fails only on new findings.

    auth-before-data

  • Criticalmedium confidenceServer Action getStripeAccountDetails (server-actions/stripe/account.ts) can reach payments without authenticating.

    Anyone, signed in or not, can read payments — a table that holds sensitive data.

    Data reached
    payments (read) · sensitive table
    Missing
    Authentication before the handler reaches data, and a query scoped to the caller.
    Confidence
    middleware.ts exists. A Server Action is POSTed to the page that uses it, so if the matcher covers that page and rejects signed-out requests, this is a false positive.
    Path
    Server Action getStripeAccountDetails (server-actions/stripe/account.ts) → payments

    Prove it

    # A Server Action is a public POST endpoint. Its id ships to the browser:
    # find it in your built client chunks next to "getStripeAccountDetails".
    curl -i -X POST 'https://YOUR_APP/' \
      -H 'Next-Action: ACTION_ID' \
      -H 'content-type: text/plain;charset=UTF-8' -d '[]'
    # No cookie is sent. A 200 that runs the action means anyone can call getStripeAccountDetails.

    Fix

    "use server";
    
    export async function getStripeAccountDetails(formData: FormData) {
      // The page's own session check does not run for an action: check here.
      const user = await getCurrentUser();   // e.g. (await supabase.auth.getUser()).data.user
      if (!user) throw new Error("Unauthorized");
      // ...scope every query to user.id
    }

    Regression test

    import { getStripeAccountDetails } from "@/server-actions/stripe/account";
    
    // Make your session lookup return no user for this test.
    vi.mock("@/lib/auth", () => ({ getCurrentUser: async () => null }));
    
    test("getStripeAccountDetails refuses a signed-out caller", async () => {
      await expect(getStripeAccountDetails(new FormData())).rejects.toThrow();
    });

    Intended? This cannot be declared intentional: public sensitive data is not a design choice the check will bless. Accept it as known debt: run wyro-check --update-baseline and commit the ledger. It stays visible and CI fails only on new findings.

    auth-before-data

  • Criticalmedium confidenceServer Action createPaymentIntent (server-actions/stripe/payment.ts) can reach carts without authenticating.

    Anyone, signed in or not, can write to carts (client_secret) — personal or secret fields.

    Data reached
    carts (write) · sensitive: client_secret
    Missing
    Authentication before the handler reaches data, and a query scoped to the caller.
    Confidence
    middleware.ts exists. A Server Action is POSTed to the page that uses it, so if the matcher covers that page and rejects signed-out requests, this is a false positive.
    Path
    Server Action createPaymentIntent (server-actions/stripe/payment.ts) → carts

    Prove it

    # A Server Action is a public POST endpoint. Its id ships to the browser:
    # find it in your built client chunks next to "createPaymentIntent".
    curl -i -X POST 'https://YOUR_APP/' \
      -H 'Next-Action: ACTION_ID' \
      -H 'content-type: text/plain;charset=UTF-8' -d '[]'
    # No cookie is sent. A 200 that runs the action means anyone can call createPaymentIntent.

    Fix

    "use server";
    
    export async function createPaymentIntent(formData: FormData) {
      // The page's own session check does not run for an action: check here.
      const user = await getCurrentUser();   // e.g. (await supabase.auth.getUser()).data.user
      if (!user) throw new Error("Unauthorized");
      // ...scope every query to user.id
    }

    Regression test

    import { createPaymentIntent } from "@/server-actions/stripe/payment";
    
    // Make your session lookup return no user for this test.
    vi.mock("@/lib/auth", () => ({ getCurrentUser: async () => null }));
    
    test("createPaymentIntent refuses a signed-out caller", async () => {
      await expect(createPaymentIntent(new FormData())).rejects.toThrow();
    });

    Intended? This cannot be declared intentional: a public write is not a design choice the check will bless. Accept it as known debt: run wyro-check --update-baseline and commit the ledger. It stays visible and CI fails only on new findings.

    auth-before-data

  • Criticalmedium confidenceServer Action createPaymentIntent (server-actions/stripe/payment.ts) can reach payments without authenticating.

    Anyone, signed in or not, can read payments — a table that holds sensitive data.

    Data reached
    payments (read) · sensitive table
    Missing
    Authentication before the handler reaches data, and a query scoped to the caller.
    Confidence
    middleware.ts exists. A Server Action is POSTed to the page that uses it, so if the matcher covers that page and rejects signed-out requests, this is a false positive.
    Path
    Server Action createPaymentIntent (server-actions/stripe/payment.ts) → payments

    Prove it

    # A Server Action is a public POST endpoint. Its id ships to the browser:
    # find it in your built client chunks next to "createPaymentIntent".
    curl -i -X POST 'https://YOUR_APP/' \
      -H 'Next-Action: ACTION_ID' \
      -H 'content-type: text/plain;charset=UTF-8' -d '[]'
    # No cookie is sent. A 200 that runs the action means anyone can call createPaymentIntent.

    Fix

    "use server";
    
    export async function createPaymentIntent(formData: FormData) {
      // The page's own session check does not run for an action: check here.
      const user = await getCurrentUser();   // e.g. (await supabase.auth.getUser()).data.user
      if (!user) throw new Error("Unauthorized");
      // ...scope every query to user.id
    }

    Regression test

    import { createPaymentIntent } from "@/server-actions/stripe/payment";
    
    // Make your session lookup return no user for this test.
    vi.mock("@/lib/auth", () => ({ getCurrentUser: async () => null }));
    
    test("createPaymentIntent refuses a signed-out caller", async () => {
      await expect(createPaymentIntent(new FormData())).rejects.toThrow();
    });

    Intended? This cannot be declared intentional: public sensitive data is not a design choice the check will bless. Accept it as known debt: run wyro-check --update-baseline and commit the ledger. It stays visible and CI fails only on new findings.

    auth-before-data

  • Criticalmedium confidenceServer Action updateCart (server-actions/update-cart.ts) can reach carts without authenticating.

    Anyone, signed in or not, can write to carts (client_secret) — personal or secret fields.

    Data reached
    carts (write) · sensitive: client_secret
    Missing
    Authentication before the handler reaches data, and a query scoped to the caller.
    Confidence
    middleware.ts exists. A Server Action is POSTed to the page that uses it, so if the matcher covers that page and rejects signed-out requests, this is a false positive.
    Path
    Server Action updateCart (server-actions/update-cart.ts) → carts

    Prove it

    # A Server Action is a public POST endpoint. Its id ships to the browser:
    # find it in your built client chunks next to "updateCart".
    curl -i -X POST 'https://YOUR_APP/' \
      -H 'Next-Action: ACTION_ID' \
      -H 'content-type: text/plain;charset=UTF-8' -d '[]'
    # No cookie is sent. A 200 that runs the action means anyone can call updateCart.

    Fix

    "use server";
    
    export async function updateCart(formData: FormData) {
      // The page's own session check does not run for an action: check here.
      const user = await getCurrentUser();   // e.g. (await supabase.auth.getUser()).data.user
      if (!user) throw new Error("Unauthorized");
      // ...scope every query to user.id
    }

    Regression test

    import { updateCart } from "@/server-actions/update-cart";
    
    // Make your session lookup return no user for this test.
    vi.mock("@/lib/auth", () => ({ getCurrentUser: async () => null }));
    
    test("updateCart refuses a signed-out caller", async () => {
      await expect(updateCart(new FormData())).rejects.toThrow();
    });

    Intended? This cannot be declared intentional: a public write is not a design choice the check will bless. Accept it as known debt: run wyro-check --update-baseline and commit the ledger. It stays visible and CI fails only on new findings.

    auth-before-data

  • Highmedium confidenceServer Action getCart (server-actions/get-cart-details.ts) can reach stores without authenticating.

    Anyone can read stores.

    Data reached
    stores (read)
    Missing
    Authentication before the handler reaches data, and a query scoped to the caller.
    Confidence
    middleware.ts exists. A Server Action is POSTed to the page that uses it, so if the matcher covers that page and rejects signed-out requests, this is a false positive.
    Path
    Server Action getCart (server-actions/get-cart-details.ts) → stores

    Prove it

    # A Server Action is a public POST endpoint. Its id ships to the browser:
    # find it in your built client chunks next to "getCart".
    curl -i -X POST 'https://YOUR_APP/' \
      -H 'Next-Action: ACTION_ID' \
      -H 'content-type: text/plain;charset=UTF-8' -d '[]'
    # No cookie is sent. A 200 that runs the action means anyone can call getCart.

    Fix

    "use server";
    
    export async function getCart(formData: FormData) {
      // The page's own session check does not run for an action: check here.
      const user = await getCurrentUser();   // e.g. (await supabase.auth.getUser()).data.user
      if (!user) throw new Error("Unauthorized");
      // ...scope every query to user.id
    }

    Regression test

    import { getCart } from "@/server-actions/get-cart-details";
    
    // Make your session lookup return no user for this test.
    vi.mock("@/lib/auth", () => ({ getCurrentUser: async () => null }));
    
    test("getCart refuses a signed-out caller", async () => {
      await expect(getCart(new FormData())).rejects.toThrow();
    });

    Intended? If this data is meant to be public (a catalogue, published posts), that is a legitimate design. Accept it as known debt: run wyro-check --update-baseline and commit the ledger. It stays visible and CI fails only on new findings.

    auth-before-data

  • Highmedium confidenceServer Action getCart (server-actions/get-cart-details.ts) can reach products without authenticating.

    Anyone can read products.

    Data reached
    products (read)
    Missing
    Authentication before the handler reaches data, and a query scoped to the caller.
    Confidence
    middleware.ts exists. A Server Action is POSTed to the page that uses it, so if the matcher covers that page and rejects signed-out requests, this is a false positive.
    Path
    Server Action getCart (server-actions/get-cart-details.ts) → products

    Prove it

    # A Server Action is a public POST endpoint. Its id ships to the browser:
    # find it in your built client chunks next to "getCart".
    curl -i -X POST 'https://YOUR_APP/' \
      -H 'Next-Action: ACTION_ID' \
      -H 'content-type: text/plain;charset=UTF-8' -d '[]'
    # No cookie is sent. A 200 that runs the action means anyone can call getCart.

    Fix

    "use server";
    
    export async function getCart(formData: FormData) {
      // The page's own session check does not run for an action: check here.
      const user = await getCurrentUser();   // e.g. (await supabase.auth.getUser()).data.user
      if (!user) throw new Error("Unauthorized");
      // ...scope every query to user.id
    }

    Regression test

    import { getCart } from "@/server-actions/get-cart-details";
    
    // Make your session lookup return no user for this test.
    vi.mock("@/lib/auth", () => ({ getCurrentUser: async () => null }));
    
    test("getCart refuses a signed-out caller", async () => {
      await expect(getCart(new FormData())).rejects.toThrow();
    });

    Intended? If this data is meant to be public (a catalogue, published posts), that is a legitimate design. Accept it as known debt: run wyro-check --update-baseline and commit the ledger. It stays visible and CI fails only on new findings.

    auth-before-data

  • Highmedium confidenceServer Action getDetailsOfProductsOrdered (server-actions/orders.ts) can reach products without authenticating.

    Anyone can read products.

    Data reached
    products (read)
    Missing
    Authentication before the handler reaches data, and a query scoped to the caller.
    Confidence
    middleware.ts exists. A Server Action is POSTed to the page that uses it, so if the matcher covers that page and rejects signed-out requests, this is a false positive.
    Path
    Server Action getDetailsOfProductsOrdered (server-actions/orders.ts) → products

    Prove it

    # A Server Action is a public POST endpoint. Its id ships to the browser:
    # find it in your built client chunks next to "getDetailsOfProductsOrdered".
    curl -i -X POST 'https://YOUR_APP/' \
      -H 'Next-Action: ACTION_ID' \
      -H 'content-type: text/plain;charset=UTF-8' -d '[]'
    # No cookie is sent. A 200 that runs the action means anyone can call getDetailsOfProductsOrdered.

    Fix

    "use server";
    
    export async function getDetailsOfProductsOrdered(formData: FormData) {
      // The page's own session check does not run for an action: check here.
      const user = await getCurrentUser();   // e.g. (await supabase.auth.getUser()).data.user
      if (!user) throw new Error("Unauthorized");
      // ...scope every query to user.id
    }

    Regression test

    import { getDetailsOfProductsOrdered } from "@/server-actions/orders";
    
    // Make your session lookup return no user for this test.
    vi.mock("@/lib/auth", () => ({ getCurrentUser: async () => null }));
    
    test("getDetailsOfProductsOrdered refuses a signed-out caller", async () => {
      await expect(getDetailsOfProductsOrdered(new FormData())).rejects.toThrow();
    });

    Intended? If this data is meant to be public (a catalogue, published posts), that is a legitimate design. Accept it as known debt: run wyro-check --update-baseline and commit the ledger. It stays visible and CI fails only on new findings.

    auth-before-data

  • Highmedium confidenceServer Action getProductsBySearchTerm (server-actions/product-search.ts) can reach products without authenticating.

    Anyone can read products.

    Data reached
    products (read)
    Missing
    Authentication before the handler reaches data, and a query scoped to the caller.
    Confidence
    middleware.ts exists. A Server Action is POSTed to the page that uses it, so if the matcher covers that page and rejects signed-out requests, this is a false positive.
    Path
    Server Action getProductsBySearchTerm (server-actions/product-search.ts) → products

    Prove it

    # A Server Action is a public POST endpoint. Its id ships to the browser:
    # find it in your built client chunks next to "getProductsBySearchTerm".
    curl -i -X POST 'https://YOUR_APP/' \
      -H 'Next-Action: ACTION_ID' \
      -H 'content-type: text/plain;charset=UTF-8' -d '[]'
    # No cookie is sent. A 200 that runs the action means anyone can call getProductsBySearchTerm.

    Fix

    "use server";
    
    export async function getProductsBySearchTerm(formData: FormData) {
      // The page's own session check does not run for an action: check here.
      const user = await getCurrentUser();   // e.g. (await supabase.auth.getUser()).data.user
      if (!user) throw new Error("Unauthorized");
      // ...scope every query to user.id
    }

    Regression test

    import { getProductsBySearchTerm } from "@/server-actions/product-search";
    
    // Make your session lookup return no user for this test.
    vi.mock("@/lib/auth", () => ({ getCurrentUser: async () => null }));
    
    test("getProductsBySearchTerm refuses a signed-out caller", async () => {
      await expect(getProductsBySearchTerm(new FormData())).rejects.toThrow();
    });

    Intended? If this data is meant to be public (a catalogue, published posts), that is a legitimate design. Accept it as known debt: run wyro-check --update-baseline and commit the ledger. It stays visible and CI fails only on new findings.

    auth-before-data

  • Highmedium confidenceServer Action deleteProduct (server-actions/products.ts) can reach products without authenticating.

    An unauthenticated caller can write to products. Writes are how data gets corrupted or planted.

    Data reached
    products (write)
    Missing
    Authentication before the handler reaches data, and a query scoped to the caller.
    Confidence
    middleware.ts exists. A Server Action is POSTed to the page that uses it, so if the matcher covers that page and rejects signed-out requests, this is a false positive.
    Path
    Server Action deleteProduct (server-actions/products.ts) → products

    Prove it

    # A Server Action is a public POST endpoint. Its id ships to the browser:
    # find it in your built client chunks next to "deleteProduct".
    curl -i -X POST 'https://YOUR_APP/' \
      -H 'Next-Action: ACTION_ID' \
      -H 'content-type: text/plain;charset=UTF-8' -d '[]'
    # No cookie is sent. A 200 that runs the action means anyone can call deleteProduct.

    Fix

    "use server";
    
    export async function deleteProduct(formData: FormData) {
      // The page's own session check does not run for an action: check here.
      const user = await getCurrentUser();   // e.g. (await supabase.auth.getUser()).data.user
      if (!user) throw new Error("Unauthorized");
      // ...scope every query to user.id
    }

    Regression test

    import { deleteProduct } from "@/server-actions/products";
    
    // Make your session lookup return no user for this test.
    vi.mock("@/lib/auth", () => ({ getCurrentUser: async () => null }));
    
    test("deleteProduct refuses a signed-out caller", async () => {
      await expect(deleteProduct(new FormData())).rejects.toThrow();
    });

    Intended? This cannot be declared intentional: a public write is not a design choice the check will bless. Accept it as known debt: run wyro-check --update-baseline and commit the ledger. It stays visible and CI fails only on new findings.

    auth-before-data

  • Highmedium confidenceServer Action getStoreSlug (server-actions/store-details.tsx) can reach stores without authenticating.

    Anyone can read stores.

    Data reached
    stores (read)
    Missing
    Authentication before the handler reaches data, and a query scoped to the caller.
    Confidence
    middleware.ts exists. A Server Action is POSTed to the page that uses it, so if the matcher covers that page and rejects signed-out requests, this is a false positive.
    Path
    Server Action getStoreSlug (server-actions/store-details.tsx) → stores

    Prove it

    # A Server Action is a public POST endpoint. Its id ships to the browser:
    # find it in your built client chunks next to "getStoreSlug".
    curl -i -X POST 'https://YOUR_APP/' \
      -H 'Next-Action: ACTION_ID' \
      -H 'content-type: text/plain;charset=UTF-8' -d '[]'
    # No cookie is sent. A 200 that runs the action means anyone can call getStoreSlug.

    Fix

    "use server";
    
    export async function getStoreSlug(formData: FormData) {
      // The page's own session check does not run for an action: check here.
      const user = await getCurrentUser();   // e.g. (await supabase.auth.getUser()).data.user
      if (!user) throw new Error("Unauthorized");
      // ...scope every query to user.id
    }

    Regression test

    import { getStoreSlug } from "@/server-actions/store-details";
    
    // Make your session lookup return no user for this test.
    vi.mock("@/lib/auth", () => ({ getCurrentUser: async () => null }));
    
    test("getStoreSlug refuses a signed-out caller", async () => {
      await expect(getStoreSlug(new FormData())).rejects.toThrow();
    });

    Intended? If this data is meant to be public (a catalogue, published posts), that is a legitimate design. Accept it as known debt: run wyro-check --update-baseline and commit the ledger. It stays visible and CI fails only on new findings.

    auth-before-data

  • Mediumlow confidenceServer Action addToCart (server-actions/add-to-cart.ts) has no validator or rate limiter attached.

    Server Action addToCart (server-actions/add-to-cart.ts) is a public POST endpoint: it accepts whatever arguments a caller sends, as often as they send them.

    Missing
    Schema validation of the action's arguments, and a rate limit.
    Confidence
    Inline validation (zod.parse on the form data) and platform rate limits are not always visible to the parser. Check before acting.

    Fix

    const Input = z.object({ title: z.string().min(1).max(200) });
    
    export async function addToCart(formData: FormData) {
      const parsed = Input.safeParse(Object.fromEntries(formData));
      if (!parsed.success) return { error: parsed.error.flatten() };
      // ...use parsed.data, never formData directly
    }

    Intended? If validation and rate limiting happen upstream (Vercel Firewall, a middleware), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Mediumlow confidenceServer Action getCart (server-actions/get-cart-details.ts) has no validator or rate limiter attached.

    Server Action getCart (server-actions/get-cart-details.ts) is a public POST endpoint: it accepts whatever arguments a caller sends, as often as they send them.

    Missing
    Schema validation of the action's arguments, and a rate limit.
    Confidence
    Inline validation (zod.parse on the form data) and platform rate limits are not always visible to the parser. Check before acting.

    Fix

    const Input = z.object({ title: z.string().min(1).max(200) });
    
    export async function getCart(formData: FormData) {
      const parsed = Input.safeParse(Object.fromEntries(formData));
      if (!parsed.success) return { error: parsed.error.flatten() };
      // ...use parsed.data, never formData directly
    }

    Intended? If validation and rate limiting happen upstream (Vercel Firewall, a middleware), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Mediumlow confidenceServer Action getDetailsOfProductsOrdered (server-actions/orders.ts) has no validator or rate limiter attached.

    Server Action getDetailsOfProductsOrdered (server-actions/orders.ts) is a public POST endpoint: it accepts whatever arguments a caller sends, as often as they send them.

    Missing
    Schema validation of the action's arguments, and a rate limit.
    Confidence
    Inline validation (zod.parse on the form data) and platform rate limits are not always visible to the parser. Check before acting.

    Fix

    const Input = z.object({ title: z.string().min(1).max(200) });
    
    export async function getDetailsOfProductsOrdered(formData: FormData) {
      const parsed = Input.safeParse(Object.fromEntries(formData));
      if (!parsed.success) return { error: parsed.error.flatten() };
      // ...use parsed.data, never formData directly
    }

    Intended? If validation and rate limiting happen upstream (Vercel Firewall, a middleware), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Mediumlow confidenceServer Action getProductsBySearchTerm (server-actions/product-search.ts) has no validator or rate limiter attached.

    Server Action getProductsBySearchTerm (server-actions/product-search.ts) is a public POST endpoint: it accepts whatever arguments a caller sends, as often as they send them.

    Missing
    Schema validation of the action's arguments, and a rate limit.
    Confidence
    Inline validation (zod.parse on the form data) and platform rate limits are not always visible to the parser. Check before acting.

    Fix

    const Input = z.object({ title: z.string().min(1).max(200) });
    
    export async function getProductsBySearchTerm(formData: FormData) {
      const parsed = Input.safeParse(Object.fromEntries(formData));
      if (!parsed.success) return { error: parsed.error.flatten() };
      // ...use parsed.data, never formData directly
    }

    Intended? If validation and rate limiting happen upstream (Vercel Firewall, a middleware), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Mediumlow confidenceServer Action deleteProduct (server-actions/products.ts) has no validator or rate limiter attached.

    Server Action deleteProduct (server-actions/products.ts) is a public POST endpoint: it accepts whatever arguments a caller sends, as often as they send them.

    Missing
    Schema validation of the action's arguments, and a rate limit.
    Confidence
    Inline validation (zod.parse on the form data) and platform rate limits are not always visible to the parser. Check before acting.

    Fix

    const Input = z.object({ title: z.string().min(1).max(200) });
    
    export async function deleteProduct(formData: FormData) {
      const parsed = Input.safeParse(Object.fromEntries(formData));
      if (!parsed.success) return { error: parsed.error.flatten() };
      // ...use parsed.data, never formData directly
    }

    Intended? If validation and rate limiting happen upstream (Vercel Firewall, a middleware), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Mediumlow confidenceServer Action getStoreSlug (server-actions/store-details.tsx) has no validator or rate limiter attached.

    Server Action getStoreSlug (server-actions/store-details.tsx) is a public POST endpoint: it accepts whatever arguments a caller sends, as often as they send them.

    Missing
    Schema validation of the action's arguments, and a rate limit.
    Confidence
    Inline validation (zod.parse on the form data) and platform rate limits are not always visible to the parser. Check before acting.

    Fix

    const Input = z.object({ title: z.string().min(1).max(200) });
    
    export async function getStoreSlug(formData: FormData) {
      const parsed = Input.safeParse(Object.fromEntries(formData));
      if (!parsed.success) return { error: parsed.error.flatten() };
      // ...use parsed.data, never formData directly
    }

    Intended? If validation and rate limiting happen upstream (Vercel Firewall, a middleware), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Mediumlow confidenceServer Action getStripeAccountDetails (server-actions/stripe/account.ts) has no validator or rate limiter attached.

    Server Action getStripeAccountDetails (server-actions/stripe/account.ts) is a public POST endpoint: it accepts whatever arguments a caller sends, as often as they send them.

    Missing
    Schema validation of the action's arguments, and a rate limit.
    Confidence
    Inline validation (zod.parse on the form data) and platform rate limits are not always visible to the parser. Check before acting.

    Fix

    const Input = z.object({ title: z.string().min(1).max(200) });
    
    export async function getStripeAccountDetails(formData: FormData) {
      const parsed = Input.safeParse(Object.fromEntries(formData));
      if (!parsed.success) return { error: parsed.error.flatten() };
      // ...use parsed.data, never formData directly
    }

    Intended? If validation and rate limiting happen upstream (Vercel Firewall, a middleware), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Mediumlow confidenceServer Action createPaymentIntent (server-actions/stripe/payment.ts) has no validator or rate limiter attached.

    Server Action createPaymentIntent (server-actions/stripe/payment.ts) is a public POST endpoint: it accepts whatever arguments a caller sends, as often as they send them.

    Missing
    Schema validation of the action's arguments, and a rate limit.
    Confidence
    Inline validation (zod.parse on the form data) and platform rate limits are not always visible to the parser. Check before acting.

    Fix

    const Input = z.object({ title: z.string().min(1).max(200) });
    
    export async function createPaymentIntent(formData: FormData) {
      const parsed = Input.safeParse(Object.fromEntries(formData));
      if (!parsed.success) return { error: parsed.error.flatten() };
      // ...use parsed.data, never formData directly
    }

    Intended? If validation and rate limiting happen upstream (Vercel Firewall, a middleware), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Mediumlow confidenceServer Action updateCart (server-actions/update-cart.ts) has no validator or rate limiter attached.

    Server Action updateCart (server-actions/update-cart.ts) is a public POST endpoint: it accepts whatever arguments a caller sends, as often as they send them.

    Missing
    Schema validation of the action's arguments, and a rate limit.
    Confidence
    Inline validation (zod.parse on the form data) and platform rate limits are not always visible to the parser. Check before acting.

    Fix

    const Input = z.object({ title: z.string().min(1).max(200) });
    
    export async function updateCart(formData: FormData) {
      const parsed = Input.safeParse(Object.fromEntries(formData));
      if (!parsed.success) return { error: parsed.error.flatten() };
      // ...use parsed.data, never formData directly
    }

    Intended? If validation and rate limiting happen upstream (Vercel Firewall, a middleware), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

1 place could not be parsed, so any route or table declared there is missing from this report:

  • app/api/uploadthing/route.ts — Next route file exports no recognised method handler

Free account, no card. The repository opens as an editable graph.

Add this check to the README

wyro architecture badge
[![wyro architecture](https://wyro.in/api/badge/jackblatch/OneStopShop)](https://wyro.in/scan/jackblatch/OneStopShop)

It updates itself whenever the repository changes and links back to this report.

What this is

Wyro reads the repository’s routes and data models and checks the paths between them: whether a route can reach a table without passing a guard, whether a datastore holding personal data is exposed to a public read, whether an endpoint that issues credentials requires the credentials it issues.

It is rule-based, not a model. The same commit produces the same result every time, and it does not guess at business rules it cannot see. A rule with nothing to look at is reported as not having run — never as a pass.

This check runs on public source through GitHub’s own API and needs no account. A free account adds the editable architecture canvas, private repositories on paid plans, and a CI gate. No card required.

Maintain this repository? You can have this report removed, and a real vulnerability is disclosed to you privately before it is published. How public reports are handled.