Skip to main content
Glama

price_manipulation_test

Test server-side price validation by sending manipulated price values like 0, 1, and -1 to identify vulnerabilities in e-commerce systems.

Instructions

Test client-side price manipulation by sending modified price values.

Sends price=0, price=1, price=-1, and negative quantity variants to check if the server validates prices server-side.

Returns: {"results": [{"test_case": str, "payload": str, "status": int, "length": int, "accepted": bool, "snippet": str}]}.

Side effects: May add items to cart or create orders at manipulated prices.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlYesURL that processes the purchase/cart action
price_paramYesParameter name for the price, e.g. 'price', 'amount', 'total'
cart_endpointNoSeparate cart/checkout endpoint to verify final price after manipulation
extra_paramsNoAdditional form parameters, e.g. 'productId=1&quantity=1'
auth_cookieNoSession cookie for authenticated requests
content_typeNoRequest content type: 'form' or 'json'

Implementation Reference

  • The `price_manipulation_test` tool definition and handler implementation in `src/tools/bizlogic.ts`.
    server.tool(
      "price_manipulation_test",
      "Test client-side price manipulation by sending modified price values.\n\nSends price=0, price=1, price=-1, and negative quantity variants to check if the server validates prices server-side.\n\nReturns: {\"results\": [{\"test_case\": str, \"payload\": str, \"status\": int, \"length\": int, \"accepted\": bool, \"snippet\": str}]}.\n\nSide effects: May add items to cart or create orders at manipulated prices.",
      {
        url: z
          .string()
          .describe("URL that processes the purchase/cart action"),
        price_param: z
          .string()
          .describe(
            "Parameter name for the price, e.g. 'price', 'amount', 'total'"
          ),
        cart_endpoint: z
          .string()
          .optional()
          .describe(
            "Separate cart/checkout endpoint to verify final price after manipulation"
          ),
        extra_params: z
          .string()
          .optional()
          .describe(
            "Additional form parameters, e.g. 'productId=1&quantity=1'"
          ),
        auth_cookie: z
          .string()
          .optional()
          .describe("Session cookie for authenticated requests"),
        content_type: z
          .string()
          .describe("Request content type: 'form' or 'json'")
          .optional(),
      },
      async ({
        url,
        price_param,
        cart_endpoint,
        extra_params,
        auth_cookie,
        content_type = "form",
      }) => {
        requireTool("curl");
    
        const testCases: Array<[string, Record<string, string>]> = [
          ["zero_price", { [price_param]: "0" }],
          ["one_cent", { [price_param]: "0.01" }],
          ["one_unit", { [price_param]: "1" }],
          ["negative_price", { [price_param]: "-100" }],
          ["large_negative", { [price_param]: "-99999" }],
          ["string_value", { [price_param]: "free" }],
        ];
    
        // If there's a quantity parameter embedded in extra_params, also test negative quantity
        if (extra_params && extra_params.includes("quantity")) {
          testCases.push([
            "negative_quantity",
            { [price_param]: "100", quantity: "-16" },
          ]);
        }
    
        const results: Array<{
          test_case: string;
          payload: Record<string, string>;
          status: number;
          length: number;
          accepted: boolean;
          response_snippet: string;
        }> = [];
    
        for (const [testName, params] of testCases) {
          let curlArgs: string[];
    
          if (content_type === "json") {
            let baseParams: Record<string, string> = {};
            if (extra_params) {
              // Parse extra_params as key=value pairs
              for (const pair of extra_params.split("&")) {
                if (pair.includes("=")) {
                  const eqIdx = pair.indexOf("=");
                  const k = pair.slice(0, eqIdx);
                  const v = pair.slice(eqIdx + 1);
                  baseParams[k] = v;
                }
              }
            }
            Object.assign(baseParams, params);
            const data = JSON.stringify(baseParams);
    
            curlArgs = [
              "-sk",
              "-o",
              "-",
              "-w",
              "\n__META__%{http_code}:%{size_download}",
              "-X",
              "POST",
              "-H",
              "Content-Type: application/json",
              "-d",
              data,
            ];
          } else {
            const formParts: string[] = [];
            if (extra_params) {
              formParts.push(extra_params);
            }
            for (const [k, v] of Object.entries(params)) {
              formParts.push(`${k}=${v}`);
            }
            const formData = formParts.join("&");
    
            curlArgs = [
              "-sk",
              "-o",
              "-",
              "-w",
              "\n__META__%{http_code}:%{size_download}",
              "-X",
              "POST",
              "-d",
              formData,
            ];
          }
    
          if (auth_cookie) {
            curlArgs.push("-b", auth_cookie);
          }
          curlArgs.push(url);
    
          const res = await runCmd("curl", curlArgs);
          let body = res.stdout;
          const metaMarker = body.lastIndexOf("__META__");
          let status = 0;
          let length = 0;
          if (metaMarker !== -1) {
            const meta = body.slice(metaMarker + 8).trim();
            const parts = meta.split(":");
            status = parts[0] ? parseInt(parts[0], 10) : 0;
            length = parts[1] ? parseInt(parts[1], 10) : 0;
            body = body.slice(0, metaMarker);
          }
    
          // Accepted if not an error status
          const accepted = [200, 201, 301, 302, 303].includes(status);
          results.push({
            test_case: testName,
            payload: params,
            status,
            length,
            accepted,
            response_snippet: body.slice(0, 500),
          });
        }
    
        // Optionally check the cart to see if manipulated prices stuck
        let cartResult: string | null = null;
        if (cart_endpoint) {
          const cartArgs = [
            "-sk",
            "-o",
            "-",
            "-w",
            "\n__META__%{http_code}:%{size_download}",
          ];
          if (auth_cookie) {
            cartArgs.push("-b", auth_cookie);
          }
          cartArgs.push(cart_endpoint);
    
          const cartRes = await runCmd("curl", cartArgs);
          let cartBody = cartRes.stdout;
          const metaMarker = cartBody.lastIndexOf("__META__");
          if (metaMarker !== -1) {
            cartBody = cartBody.slice(0, metaMarker);
          }
          cartResult = cartBody.slice(0, 1000);
        }
    
        const acceptedCount = results.filter((r) => r.accepted).length;
        const result = {
          results,
          accepted_count: acceptedCount,
          cart_contents: cartResult,
          hint:
            acceptedCount > 0
              ? `${acceptedCount} manipulated price(s) accepted. Check cart for final totals.`
              : "All manipulated prices rejected. Server-side validation appears intact.",
        };
    
        return { content: [{ type: "text", text: JSON.stringify(result) }] };
      }
    );

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/operantlabs/operant-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server