geo-sleuthBLOCK
An agent skill that finds where a photo was taken — OpenStreetMap geometry, elevation skylines, satellite imagery and street view — and shows its work. Works with Claude Code, Codex, Cursor, Gemini CLI, OpenCode and GitHub Copilot.
Overview
From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.
Global lookup tables. clues.py lookup reads the JSON in this directory; country tables (plates, area codes, admin divisions) live in region packs (regions//data/, format in regions/README.md). Each file's _meta records the source URL, fetch date, and entry count; maintainers refetch with refresh.py (global tables and the China pack).
Notes:
- The Wikipedia-sourced tables are factual data scraped and compiled from the tables in the corresponding articles. Wikipedia text is licensed under CC BY-SA 4.0; these tables are released under the same license, with attribution to Wikipedia and its editors.
country_names.jsonis a hand-compiled mapping table; the keys ofen2zhmatch the English spellings used in the other tables, andaliasesmaps short names, traditional-character names, former names, and English abbreviations toen2zhkeys.- This directory contains no OpenStreetMap data;
gazetteer.pyandosm.pyquery Overpass live.
Source names and quoted source notes stay in their original langua
4b228a000668OBSERVED · 2026-10-19Install
Commands as the repository documents them. They are shown, not run.
uv run ${CLAUDE_SKILL_DIR}/scripts/intake.py photo.jpg --out-dir intake/ [--box x0,y0,x1,y1 ...]uv run ${CLAUDE_SKILL_DIR}/scripts/board.py init --photo photo.jpguv run ${CLAUDE_SKILL_DIR}/scripts/board.py children <parent admin area> # municipality → all districts; country →uv run ${CLAUDE_SKILL_DIR}/scripts/board.py evidence --clue K1 --for <candidate A>:5 --for <candidate B>:2 --against <candiuv run ${CLAUDE_SKILL_DIR}/scripts/board.py rankuv run ${CLAUDE_SKILL_DIR}/scripts/board.py nextHost compatibility
What the documentation claims. We have not run a compatibility test.
| Host | Status | Notes |
|---|---|---|
| claude-code | mentioned |
What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
---
name: geo-sleuth
description: Geolocate or chronolocate a photo with tool-verified reasoning (where was this taken / when was it taken / photo geolocation / geo-guessing). Given one or more photos, intake.py does metadata, OCR and reverse image search (Baidu/Yandex) in one command; clues and candidates go on a candidate board (board.py) that ranks them and gives the next step; clues.py looks up calling codes, driving side, plates and area codes from region packs; for satellite and street view the machine ranks first and you look only at the top few (CLIP in sat_scan.py, DINOv2+SIFT in match.py); plus sun and shadow math, OSM Overpass and DEM skylines. Every conclusion is checked against real data; output is coordinates + error radius, evidence images and tiered confidence. Use when the user shares a photo and asks where was this taken / geolocate this / when was this taken / 这是哪 / 在哪拍的 / 帮我定位这张照片 / 网络迷踪 / 图寻 / 几点拍的.
---
# geo-sleuth (v2)
Goal: find a location that **holds up under checking**. Every conclusion must point to the clue it used, the command it ran and the file it produced.
> `${CLAUDE_SKILL_DIR}` in commands means the directory containing this SKILL.md (the same for commands in references). Claude Code substitutes it automatically; in other agents, first `export CLAUDE_SKILL_DIR=<absolute path of this directory>` before running (if the shell doesn't keep variables between commands, prefix every command with it), or replace it with that path directly.
The work is split into three layers. Remember this first, then read the flow:
| Layer | Who | Tools |
|---|---|---|
| Decision: which candidates exist, how evidence is scored, whether a candidate can be excluded, what to scan next | **Script** (candidate board) | `board.py` |
| Perception: reading text, table lookups, finding targets in satellite imagery, street-view matching | **Scripts compute and rank first; you look only at the top few** | `intake.py` `ocr.py` `clues.py` `sat_scan.py` `match.py` `geo.py bearings` |
| Judgment: pulling clues out of the image, proposing hypotheses when the tables have nothing, deciding among the machine's top few | **You** | — |
The method comes from breaking down 14 photo-geolocation (网络迷踪) creator videos and 22 puzzles, plus several rounds of blind-test comparisons; the breakdown notes are not published with the repo. The lesson of v1: rules written as prose don't get executed, and two runs of the same skill version gave very different results; so v2 puts every rule that can be written as code into `board.py`.
## Hard rules (always in force; ★ = enforced by board.py, just follow it)
0. If the user says the photo isn't theirs, or the image shows a private residence or minors, ask once what it's for before continuing; when the prompt already states the source or purpose (an evaluation puzzle, a puzzle setter's hint, the user says they took it), don't ask, and go to the finest level as usual.
1. **Don't fabricate verification.** "Measured on the map", "matched street view", "±10 m" must correspond to commands actually run and files actually produced in this session. If you didn't run it, write "unverified".
2. **Precision needs a source**: an error radius ≤100 m requires the intersection of two independent constraints, or a ground-level match on ≥3 invariant features. Before reporting a camera position, run a self-check: `pose.py project --horizon <sea-horizon row>` computes what the pitch should be and each feature's "expected row" for the current camera position; reconcile that with the photo (the depression angle to a target ≠ the camera's pitch; look up the camera position's elevation with `terrain.py elev` — if the height difference doesn't match the depression angles in the image, the camera position is wrong).
3. **Once you have a unique anchor, close the loop**: all later search and geometry starts from the anchor; don't go back to generic features like "blue-green lake water" or "tropical park" and pick the most famous place of that kind.
4. **Compute bearings first, then identify structures**: which block in satellite imagery is the tower or chimney in the photo is itself an interpretation. First compute its true bearing in the image with `sun.py compass` or a confirmed landmark, then use `geo.py bearings` to see which outline around the candidate camera position falls on that bearing; when nothing fits, first try both templates ("original/mirrored", "sunrise/sunset"); if not verified, only lower the tier, don't exclude.
5. ★ **List the whole category before inferring**: when inferring a region from "hill city", "tropical", "IP in a municipality", use `board.py children` to add all subordinate admin divisions as candidates, then rank them with evidence. Don't default to the main urban district, don't pick by population.
6. **Metadata and hints are hypotheses**: when EXIF, IP location, location tags or the puzzle setter's words conflict with the image, the image wins; don't invent a story to reconcile them. IP says country A but the hint or the regulation designs (plates, signs) point to another continent: intersect with `clues.py lookup territories <country A> --continent <continent>`.
7. **Self-consistency**: redo each key judgment once with a different crop or keyword set; if the two results differ by tens of kilometers or more, lower the tier.
8. ★ **If you can't pin a point, give a range + what information is missing**; when the candidates are a few discrete ones, don't take the midpoint: the main answer of `board.py report` is always the top-ranked candidate; the rest go into alternatives with discriminating tests.
9. ★ **Exclusion and confirmation use the same standard**: `board.py exclude` only accepts read/computed clues + a computed file (output of `geo.py frame`, `terrain.py`, etc.); observed and inferred clues can only down-weight via `evidence --against`, with the likelihood ratio clamped to 1/3–3 (inferred) or 1/5–5 (observed). Generating candidates from "thereTrust audit
BLOCKgrade D · trust 69/100 Do not install this without reading the findings. The audit found something that could harm you or your machine.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | WARN |
| L2 | Instruction surface (what it tells the agent) | FAIL |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- declared (9 observation(s))
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (9)
- Counterexamples: vehicles travel and are sold across provinces, and existing plates often keep their code even after a sale; plates in the older format (e.g. `ශ්රී` between the numbers) have no code
print(f"{name:24s} rms {v['rms_px']:6.1f} px Δchi2 {v['delta_chi2']:8.1f} robust Δchi2 {v['robust_delta_chi2']:8.1f} ({v['robust_by']})"print(f"-> {args.save} (only robust Δchi2 > 9 counts as inconsistent: dropping any one point can't rescue it; when two candidates differ only on one point, verify that point first)")key = cache / (hashlib.sha1(ql.encode()).hexdigest()[:16] + ".json")
key = cache / (hashlib.sha1(ql.encode()).hexdigest()[:16] + '.json')
key = cache / (hashlib.sha1(QH.encode()).hexdigest()[:16] + '.json')
base = f"http://127.0.0.1:{server.server_port}"with patch.dict(os.environ, {"HTTP_PROXY": "http://127.0.0.1:1", "http_proxy": "http://127.0.0.1:1","ALL_PROXY": "http://127.0.0.1:1", "NO_PROXY": "*", "GEO_PROXY": "http://127.0.0.1:1"}):
Gates applied: no_behavioural_pass.
4b228a000668full audit observations/trust-audit/skill/oldcircle__geo-sleuth.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-19 | 4b228a000668 | BLOCK | D | 69 | first audit |
Questions
What does the geo-sleuth skill do?
An agent skill that finds where a photo was taken — OpenStreetMap geometry, elevation skylines, satellite imagery and street view — and shows its work. Works with Claude Code, Codex, Cursor, Gemini CLI, OpenCode and GitHub Copilot.
Is geo-sleuth safe to install?
No — not without reading the findings first. The audit graded it D (69/100) and found 1 critical or high issue in the source. Each one is listed on this page with the file and line it is on.
What can geo-sleuth access on my machine?
The audit observed that it reaches the network. Each of those is consistent with what it says it does. Secrets in the source: none found.
Which assistants does geo-sleuth work with?
Its documentation mentions claude-code. That is what the text claims, not a compatibility test we ran.
How current is this page?
The grade is for one exact copy of the source (4b228a000668), read on 2026-10-19. The repository is watched, and a new audit runs when it changes — this is the first audit.
Add this badge to your README
Shows this page's audit result and links back to it. It updates when the audit does.
[](https://modelsatlas.com/skills/oldcircle/geo-sleuth/)
Not your listing, or something out of date? Claim or update your listing.