Atlas / Skills / oldcircle / geo-sleuth

geo-sleuthBLOCK

skills/oldcircle/geo-sleuth

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.

Verdict
BLOCK
Grade
D
Trust score
69 /100
Version
—
Hosts
1 documented
License
MIT
Stars
3,465
01

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.json is a hand-compiled mapping table; the keys of en2zh match the English spellings used in the other tables, and aliases maps short names, traditional-character names, former names, and English abbreviations to en2zh keys.
  • This directory contains no OpenStreetMap data; gazetteer.py and osm.py query Overpass live.

Source names and quoted source notes stay in their original langua

Read from source at commit 4b228a000668OBSERVED · 2026-10-19
Advertisement
02

Install

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.jpg
uv 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 <candi
uv run ${CLAUDE_SKILL_DIR}/scripts/board.py rank
uv run ${CLAUDE_SKILL_DIR}/scripts/board.py next
03

Host compatibility

What the documentation claims. We have not run a compatibility test.

HostStatusNotes
claude-codementioned
04

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 "there
05

Trust 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.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codeWARN
L2Instruction surface (what it tells the agent)FAIL
L3Class-specific surfacePASS
L4Behavioural (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)

HIGHPrompt injection · prompt.zero_width · CWE-94, CWE-1427
regions/lk/clues.md:11
- 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
Why it matters. invisible characters in instruction text
Fix. strip non-printing characters
MEDIUMObfuscation / stealth · obf.homoglyph · CWE-506, CWE-94
scripts/pose.py:403
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']})"
MEDIUMObfuscation / stealth · obf.homoglyph · CWE-506, CWE-94
scripts/pose.py:405
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)")
LOWInsecure crypto · crypto.weak_hash · CWE-327, CWE-338
scripts/osm.py:104
key = cache / (hashlib.sha1(ql.encode()).hexdigest()[:16] + ".json")
LOWInsecure crypto · crypto.weak_hash · CWE-327, CWE-338
tests/test_runtime.py:250
key = cache / (hashlib.sha1(ql.encode()).hexdigest()[:16] + '.json')
LOWInsecure crypto · crypto.weak_hash · CWE-327, CWE-338
tests/test_runtime.py:270
key = cache / (hashlib.sha1(QH.encode()).hexdigest()[:16] + '.json')
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
tests/test_runtime.py:73
base = f"http://127.0.0.1:{server.server_port}"
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
tests/test_runtime.py:75
with patch.dict(os.environ, {"HTTP_PROXY": "http://127.0.0.1:1", "http_proxy": "http://127.0.0.1:1",
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
tests/test_runtime.py:76
"ALL_PROXY": "http://127.0.0.1:1", "NO_PROXY": "*", "GEO_PROXY": "http://127.0.0.1:1"}):

Gates applied: no_behavioural_pass.

Audited 2026-10-19 · audit v0.4.1 · source sha 4b228a000668full audit observations/trust-audit/skill/oldcircle__geo-sleuth.json · Report an issue / request a re-scan
06

Audit history

Every audit this skill has had.

DateSourceVerdictGradeScoreChange
2026-10-194b228a000668BLOCKD69first audit
07

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.

ModelsAtlas audit badge

Markdown
[![ModelsAtlas audit: geo-sleuth](https://modelsatlas.com/skills/oldcircle/geo-sleuth/badge.svg)](https://modelsatlas.com/skills/oldcircle/geo-sleuth/)

Not your listing, or something out of date? Claim or update your listing.

Advertisement