{"record":{"id":"ec9fc054ae600578","repo":"Crosstalk-Solutions/project-nomad","slug":"invalid-world-basemap-path","errorCode":null,"errorMessage":"Invalid world basemap path","messagePattern":"Invalid world basemap path","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"admin/app/services/map_service.ts","lineNumber":457,"sourceCode":"   *\n   * Memoizes success in-process, and de-duplicates concurrent callers via a\n   * shared in-flight promise so two simultaneous `/maps` requests on a cold\n   * start don't both launch `pmtiles extract` against the same output path.\n   */\n  private async ensureWorldBasemap(): Promise<void> {\n    if (this.worldBasemapReady) return\n    if (this.worldBasemapInFlight) return this.worldBasemapInFlight\n    this.worldBasemapInFlight = this._setupWorldBasemap().finally(() => {\n      this.worldBasemapInFlight = null\n    })\n    return this.worldBasemapInFlight\n  }\n\n  private async _setupWorldBasemap(): Promise<void> {\n    const basePath = resolve(join(this.baseDirPath, 'pmtiles'))\n    const filepath = resolve(join(basePath, WORLD_BASEMAP_FILENAME))\n    if (!filepath.startsWith(basePath + sep)) {\n      throw new Error('Invalid world basemap path')\n    }\n\n    await ensureDirectoryExists(basePath)\n\n    const existing = await getFileStatsIfExists(filepath)\n    if (existing && Number(existing.size) > 0) {\n      this.worldBasemapReady = true\n      return\n    }\n\n    const info = await this.getGlobalMapInfo()\n    const args = buildPmtilesExtractArgs({\n      sourceUrl: info.url,\n      outputFilepath: filepath,\n      maxzoom: WORLD_BASEMAP_MAX_ZOOM,\n      downloadThreads: 4,\n    })\n","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/Crosstalk-Solutions/project-nomad/blob/0bd1c6f4f9888d577fe232de06ac144bb8337131/admin/app/services/map_service.ts#L439-L475","documentation":"_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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Keep WORLD_BASEMAP_FILENAME a bare filename with no slashes or '..' segments","Audit mapStoragePath/baseDirPath configuration for '..' or absolute overrides","Restore the constant to its shipped default and re-run ensureWorldBasemap"],"exampleFix":"// before\nconst WORLD_BASEMAP_FILENAME = '../shared/world.pmtiles'\n\n// after\nconst WORLD_BASEMAP_FILENAME = 'world.pmtiles'","handlingStrategy":"validation","validationCode":"const SAFE = /^[\\w.-]+$/\nif (!SAFE.test(WORLD_BASEMAP_FILENAME)) throw new Error('basemap filename must be a bare filename')","typeGuard":"const isBareFilename = (name: string): name is string => /^[\\w.-]+$/.test(name) && !name.includes('..')","tryCatchPattern":"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 }","preventionTips":["Never derive basemap filenames from user input","Keep config-controlled filenames restricted to [A-Za-z0-9._-]","Test path-containment guards with traversal payloads in CI"],"tags":["path-traversal","security","config"],"backgroundTag":"path-traversal-blocked","analyzedSha":"0bd1c6f4f9888d577fe232de06ac144bb8337131","analyzedAt":"2026-08-27T05:34:15.424Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}