Crosstalk-Solutions/project-nomad · critical · Error

Invalid world basemap path

Error message

Invalid world basemap path

What it means

_setupWorldBasemap resolves the destination path for WORLD_BASEMAP_FILENAME under storage/maps/pmtiles and enforces a path-containment guard: the resolved filepath must start with basePath + sep. If not, the configured filename/path escapes the pmtiles directory and setup aborts.

Source

Thrown at admin/app/services/map_service.ts:457

   *
   * Memoizes success in-process, and de-duplicates concurrent callers via a
   * shared in-flight promise so two simultaneous `/maps` requests on a cold
   * start don't both launch `pmtiles extract` against the same output path.
   */
  private async ensureWorldBasemap(): Promise<void> {
    if (this.worldBasemapReady) return
    if (this.worldBasemapInFlight) return this.worldBasemapInFlight
    this.worldBasemapInFlight = this._setupWorldBasemap().finally(() => {
      this.worldBasemapInFlight = null
    })
    return this.worldBasemapInFlight
  }

  private async _setupWorldBasemap(): Promise<void> {
    const basePath = resolve(join(this.baseDirPath, 'pmtiles'))
    const filepath = resolve(join(basePath, WORLD_BASEMAP_FILENAME))
    if (!filepath.startsWith(basePath + sep)) {
      throw new Error('Invalid world basemap path')
    }

    await ensureDirectoryExists(basePath)

    const existing = await getFileStatsIfExists(filepath)
    if (existing && Number(existing.size) > 0) {
      this.worldBasemapReady = true
      return
    }

    const info = await this.getGlobalMapInfo()
    const args = buildPmtilesExtractArgs({
      sourceUrl: info.url,
      outputFilepath: filepath,
      maxzoom: WORLD_BASEMAP_MAX_ZOOM,
      downloadThreads: 4,
    })

View on GitHub (pinned to 0bd1c6f4f9)

Solutions

  1. Keep WORLD_BASEMAP_FILENAME a bare filename with no slashes or '..' segments
  2. Audit mapStoragePath/baseDirPath configuration for '..' or absolute overrides
  3. Restore the constant to its shipped default and re-run ensureWorldBasemap

Example fix

// before
const WORLD_BASEMAP_FILENAME = '../shared/world.pmtiles'

// after
const WORLD_BASEMAP_FILENAME = 'world.pmtiles'
Defensive patterns

Strategy: validation

Validate before calling

const SAFE = /^[\w.-]+$/
if (!SAFE.test(WORLD_BASEMAP_FILENAME)) throw new Error('basemap filename must be a bare filename')

Type guard

const isBareFilename = (name: string): name is string => /^[\w.-]+$/.test(name) && !name.includes('..')

Try / catch

try { await mapService.ensureWorldBasemap() } catch (e) { if (e instanceof Error && e.message === 'Invalid world basemap path') { /* audit WORLD_BASEMAP_FILENAME / storage path config */ } throw e }

Prevention

When it happens

Trigger: WORLD_BASEMAP_FILENAME being changed (or injected) to a value containing '../' or an absolute path such as '/etc/cron.d/evil', so resolve() produces a path outside basePath. It can also fire if baseDirPath itself is misconfigured so the join result escapes the expected root.

Common situations: Custom builds that override WORLD_BASEMAP_FILENAME with a path instead of a bare filename; env-driven storage path configs that are relative or contain traversal; supply-chain/config tampering this guard is designed to catch.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of Crosstalk-Solutions/project-nomad@0bd1c6f4f9 (2026-08-27). Data as JSON: /api/errors/ec9fc054ae600578. Report an issue: GitHub.