cybersecurity · intermediate · ~15 min · safe pentest lab

Count Unique API Endpoint Paths

Canonicalize attacker-supplied identifiers before comparing them, and bounds-check the container and every pointer inside it before dereferencing — the core discipline behind safe API surface enumeration.

Challenge

API surface discovery — path canonicalization

During authorized API reconnaissance you scrape a list of endpoint paths from logs, a swagger dump, and crawler output. Many are duplicates that only look distinct: /api/v1/users and /api/v1/users/ are the same resource, differing by one trailing slash. To size the real attack surface you must count how many distinct endpoints there really are.

The asset is a fixed, in-harness array of path strings — never a live host, socket, or filesystem. The threat is a miscount that either understates the surface or, worse, a memory-safety bug: the paths list is attacker-influenced, so it may be NULL, carry a negative or zero count, or contain NULL entries. The insecure assumption to avoid is "every pointer is a valid, non-empty C string".

Task

Implement count_unique_paths. Two paths are considered equal when they match after stripping at most one trailing '/'. Only one slash is removed, so /x/ and /x// remain distinct. Deny by default: validate paths and n before use, and skip any NULL entry rather than dereferencing it.

Example

For an array sample = { "/api/v1/users", "/api/v1/users/", "/admin" }:

count_unique_paths(sample, 3) -> 2

Edge cases

  • n == 0 or n < 0 -> 0
  • paths == NULL -> 0
  • A NULL element is skipped, not counted
  • "/" collapses to the empty string, equal to ""

Input format

paths: a pointer to an array of n C-string pointers (const char *const *). Individual elements may be NULL. n: the element count (int), which may be 0 or negative.

Output format

An int: the number of distinct paths, where two paths are equal after stripping at most one trailing '/'. NULL entries are ignored.

Constraints

n may be any int (including 0 and negative). paths may be NULL. Any element may be NULL. Each non-NULL element is a NUL-terminated C string. Do not allocate; O(n^2) comparison is acceptable. Must be memory-safe under ASan/UBSan.

Starter code

int count_unique_paths(const char *const *paths, int n)
{
    /* TODO: count distinct paths (two are equal after stripping at most ONE
       trailing '/'). This insecure stub blindly trusts n and returns it
       without bounds checks or deduplication. Replace it. */
    return n;
}

Common mistakes

Returning n without deduplicating; dereferencing paths or paths[i] before NULL-checking; stripping ALL trailing slashes instead of exactly one; treating a path with a trailing slash as always distinct from its slash-less twin; using signed length underflow when a string is empty.

Edge cases to handle

n == 0 and n < 0 both return 0; paths == NULL returns 0; a NULL element is skipped; "/" normalizes to "" and equals ""; only ONE trailing slash is stripped so "/x/" != "/x//"; exact duplicates collapse to a single count.

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.