Fix path traversal, auth-bypass, and DoS findings from the security review

- Add ValidateFileName (allowlist regex, rejects .. and separators) and apply
  it to every route that concatenates a raw path segment into a filesystem or
  remote FTP path: /show/lvgmc-forecast, /show/grib, /grib/binary-chunk, and
  /debug/file. Previously an unauthenticated caller could read arbitrary
  files, including /proc/self/environ (leaks LVGMC_PASSWORD/POSTGRES_PASSWORD).
- Harden ValidateInt to reject negative integers.
- Gate /api/fetch/lvgmc/stations behind ENABLE_LVGMC_FTP_JOBS so it can no
  longer trigger a real, unauthenticated FTP login regardless of the flag;
  stop leaking error.getMessage in its response.
- Add an explicit /api/* catch-all (NotFound) so an unmatched API route can
  never fall through to the SPA fallback and be served index.html as a 200.
- Cap binary-chunk read length at 64MB to prevent an unbounded allocation.
This commit is contained in:
b0txec
2026-08-24 11:50:53 +03:00
parent fdd5508e43
commit 6b9c7cf4ae
3 changed files with 50 additions and 16 deletions
+9
View File
@@ -81,7 +81,16 @@ class DataService(log: Logger[IO]) {
Executors.newFixedThreadPool(8) // limit concurrency
)
private val MAX_BINARY_CHUNK_BYTES = 64 * 1024 * 1024
def getBinaryChunk(offset: Int, length: Int, fileName: String): IO[Array[Byte]] = {
if (length > MAX_BINARY_CHUNK_BYTES || length < 0)
IO.raiseError(new IllegalArgumentException(s"Requested length $length is invalid (max $MAX_BINARY_CHUNK_BYTES bytes)"))
else
getBinaryChunkUnchecked(offset, length, fileName)
}
private def getBinaryChunkUnchecked(offset: Int, length: Int, fileName: String): IO[Array[Byte]] = {
val fileResource = Resource.make(
IO.blocking(new RandomAccessFile(s"$GRIB_FOLDER/$fileName", "r"))
)(file => IO.blocking(file.close()))