Skip to main content
Glama

idor_test

Test for Insecure Direct Object References by iterating through IDs to detect unauthorized data access. Sends requests with different IDs and analyzes response variations to identify potential IDOR vulnerabilities.

Instructions

Test Insecure Direct Object References by iterating through IDs/GUIDs.

Sends requests with each ID and compares response status codes and lengths. Differing responses suggest IDOR — the server returns data for other users' objects without proper authorization checks.

Returns: {"baseline": dict, "results": [{"id": str, "status": int, "length": int, "different": bool, "snippet": str}], "idor_candidates": [str]}.

Side effects: Read-only requests. Sends len(id_list) + 1 requests.

Errors: ConnectionError if target unreachable.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlYesURL with ID parameter, e.g. https://target/my-account?id=123 or https://target/api/users/123
parameterYesParameter name containing the ID, e.g. 'id'. Use '__path__' if the ID is in the URL path
id_listYesList of IDs/GUIDs to test, e.g. ['1','2','3'] or ['abc-def-123', 'ghi-jkl-456']
auth_cookieNoSession cookie to send (e.g. 'session=abc123'). If None, tests without auth
methodNoHTTP method to use

Implementation Reference

  • The handler implementation for the `idor_test` tool, which iterates over IDs and compares responses against a baseline to detect potential IDOR vulnerabilities.
    async ({ url, parameter, id_list, auth_cookie, method = "GET" }) => {
      requireTool("curl");
    
      // Baseline: request with an obviously-invalid ID
      function buildUrl(testId: string): string {
        if (parameter === "__path__") {
          // ID is the last path segment — replace it (replicates Python's rsplit("/", 1))
          const lastSlash = url.lastIndexOf("/");
          if (lastSlash !== -1) {
            return `${url.slice(0, lastSlash)}/${testId}`;
          }
          return `${url}/${testId}`;
        } else {
          const base = url.split("?")[0];
          return `${base}?${parameter}=${testId}`;
        }
      }
    
      const invalidUrl = buildUrl("99999999-invalid-0000-0000-000000000000");
      const baselineArgs = [
        "-sk",
        "-o",
        "-",
        "-w",
        "\n__META__%{http_code}:%{size_download}",
        "-X",
        method,
      ];
      if (auth_cookie) {
        baselineArgs.push("-b", auth_cookie);
      }
      baselineArgs.push(invalidUrl);
    
      const baselineRes = await runCmd("curl", baselineArgs);
      let baseBody = baselineRes.stdout;
      const baseMetaMarker = baseBody.lastIndexOf("__META__");
      let baseStatus = 0;
      let baseLength = 0;
      if (baseMetaMarker !== -1) {
        const meta = baseBody.slice(baseMetaMarker + 8).trim();
        const parts = meta.split(":");
        baseStatus = parts[0] ? parseInt(parts[0], 10) : 0;
        baseLength = parts[1] ? parseInt(parts[1], 10) : 0;
      }
    
      const results: Array<{
        id: string;
        status: number;
        length: number;
        different_from_baseline: boolean;
        response_snippet: string;
      }> = [];
      const idorCandidates: string[] = [];
    
      for (const testId of id_list) {
        const targetUrl = buildUrl(testId);
        const curlArgs = [
          "-sk",
          "-o",
          "-",
          "-w",
          "\n__META__%{http_code}:%{size_download}",
          "-X",
          method,
        ];
        if (auth_cookie) {
          curlArgs.push("-b", auth_cookie);
        }
        curlArgs.push(targetUrl);
    
        const res = await runCmd("curl", curlArgs);
        let resBody = res.stdout;
        const metaMarker = resBody.lastIndexOf("__META__");
        let status = 0;
        let length = 0;
        if (metaMarker !== -1) {
          const meta = resBody.slice(metaMarker + 8).trim();
          const parts = meta.split(":");
          status = parts[0] ? parseInt(parts[0], 10) : 0;
          length = parts[1] ? parseInt(parts[1], 10) : 0;
          resBody = resBody.slice(0, metaMarker);
        }
    
        const different =
          status !== baseStatus || Math.abs(length - baseLength) > 50;
        const entry = {
          id: testId,
          status,
          length,
          different_from_baseline: different,
          response_snippet: resBody.slice(0, 500),
        };
        results.push(entry);
        if (different && status === 200) {
          idorCandidates.push(testId);
        }
      }
    
      const result = {
        baseline: { status: baseStatus, length: baseLength },
        results,
        idor_candidates: idorCandidates,
        hint:
          idorCandidates.length > 0
            ? `${idorCandidates.length} ID(s) returned different data — potential IDOR.`
            : "All IDs returned consistent responses. IDOR unlikely on this endpoint.",
      };
    
      return { content: [{ type: "text", text: JSON.stringify(result) }] };
    }
  • Registration of the `idor_test` tool using `server.tool` with Zod schema validation for inputs.
    server.tool(
      "idor_test",
      "Test Insecure Direct Object References by iterating through IDs/GUIDs.\n\nSends requests with each ID and compares response status codes and lengths. Differing responses suggest IDOR — the server returns data for other users' objects without proper authorization checks.\n\nReturns: {\"baseline\": dict, \"results\": [{\"id\": str, \"status\": int, \"length\": int, \"different\": bool, \"snippet\": str}], \"idor_candidates\": [str]}.\n\nSide effects: Read-only requests. Sends len(id_list) + 1 requests.\n\nErrors: ConnectionError if target unreachable.",
      {
        url: z
          .string()
          .describe(
            "URL with ID parameter, e.g. https://target/my-account?id=123 or https://target/api/users/123"
          ),
        parameter: z
          .string()
          .describe(
            "Parameter name containing the ID, e.g. 'id'. Use '__path__' if the ID is in the URL path"
          ),
        id_list: z
          .array(z.string())
          .min(1)
          .max(50)
          .describe(
            "List of IDs/GUIDs to test, e.g. ['1','2','3'] or ['abc-def-123', 'ghi-jkl-456']"
          ),
        auth_cookie: z
          .string()
          .optional()
          .describe(
            "Session cookie to send (e.g. 'session=abc123'). If None, tests without auth"
          ),
        method: z
          .string()
          .describe("HTTP method to use")
          .optional(),
      },
      async ({ url, parameter, id_list, auth_cookie, method = "GET" }) => {
        requireTool("curl");
    
        // Baseline: request with an obviously-invalid ID
        function buildUrl(testId: string): string {
          if (parameter === "__path__") {
            // ID is the last path segment — replace it (replicates Python's rsplit("/", 1))
            const lastSlash = url.lastIndexOf("/");
            if (lastSlash !== -1) {
              return `${url.slice(0, lastSlash)}/${testId}`;
            }
            return `${url}/${testId}`;
          } else {
            const base = url.split("?")[0];
            return `${base}?${parameter}=${testId}`;
          }
        }
    
        const invalidUrl = buildUrl("99999999-invalid-0000-0000-000000000000");
        const baselineArgs = [
          "-sk",
          "-o",
          "-",
          "-w",
          "\n__META__%{http_code}:%{size_download}",
          "-X",
          method,
        ];
        if (auth_cookie) {
          baselineArgs.push("-b", auth_cookie);
        }
        baselineArgs.push(invalidUrl);
    
        const baselineRes = await runCmd("curl", baselineArgs);
        let baseBody = baselineRes.stdout;
        const baseMetaMarker = baseBody.lastIndexOf("__META__");
        let baseStatus = 0;
        let baseLength = 0;
        if (baseMetaMarker !== -1) {
          const meta = baseBody.slice(baseMetaMarker + 8).trim();
          const parts = meta.split(":");
          baseStatus = parts[0] ? parseInt(parts[0], 10) : 0;
          baseLength = parts[1] ? parseInt(parts[1], 10) : 0;
        }
    
        const results: Array<{
          id: string;
          status: number;
          length: number;
          different_from_baseline: boolean;
          response_snippet: string;
        }> = [];
        const idorCandidates: string[] = [];
    
        for (const testId of id_list) {
          const targetUrl = buildUrl(testId);
          const curlArgs = [
            "-sk",
            "-o",
            "-",
            "-w",
            "\n__META__%{http_code}:%{size_download}",
            "-X",
            method,
          ];
          if (auth_cookie) {
            curlArgs.push("-b", auth_cookie);
          }
          curlArgs.push(targetUrl);
    
          const res = await runCmd("curl", curlArgs);
          let resBody = res.stdout;
          const metaMarker = resBody.lastIndexOf("__META__");
          let status = 0;
          let length = 0;
          if (metaMarker !== -1) {
            const meta = resBody.slice(metaMarker + 8).trim();
            const parts = meta.split(":");
            status = parts[0] ? parseInt(parts[0], 10) : 0;
            length = parts[1] ? parseInt(parts[1], 10) : 0;
            resBody = resBody.slice(0, metaMarker);
          }
    
          const different =
            status !== baseStatus || Math.abs(length - baseLength) > 50;
          const entry = {
            id: testId,
            status,
            length,
            different_from_baseline: different,
            response_snippet: resBody.slice(0, 500),
          };
          results.push(entry);
          if (different && status === 200) {
            idorCandidates.push(testId);
          }
        }
    
        const result = {
          baseline: { status: baseStatus, length: baseLength },
          results,
          idor_candidates: idorCandidates,
          hint:
            idorCandidates.length > 0
              ? `${idorCandidates.length} ID(s) returned different data — potential IDOR.`
              : "All IDs returned consistent responses. IDOR unlikely on this endpoint.",
        };
    
        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