fix(birdnet-go): reject path traversal and create parent dir for sqlite path rewrite

Addresses review feedback on the sqlite persistence fix (PR #2812):
- validate_safe_path now rejects ".." path segments so a relative
  output.sqlite.path can't traverse outside /config when rewritten.
- The rewrite now creates the destination's parent directory, since
  SQLite won't create one itself for a path like "db/birdnet.db".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KHJ4o22cdcgdNtb81tBTPJ
This commit is contained in:
Claude
2026-07-05 20:42:36 +00:00
parent dce470baf4
commit 3bbb519ed0
6 changed files with 40 additions and 6 deletions

View File

@@ -1,3 +1,6 @@
## source-20260705-3 (05-07-2026)
- Harden the `output.sqlite.path` rewrite added for the persistence fix: reject paths containing `..` traversal segments (shared `validate_safe_path` check), and create the destination's parent directory under `/config` when the relative path includes a subdirectory (e.g. `db/birdnet.db`), since SQLite does not create missing parent directories itself.
## source-20260705-2 (05-07-2026)
- Fix detections/database not persisting across restarts on a fresh install: upstream's default `config.yaml` ships `output.sqlite.path: birdnet.db` (relative) explicitly, so the missing-only (`//=`) seeding introduced previously never rewrote it to an absolute path. A relative path resolves against the app's ephemeral working directory, so the database was silently recreated empty on every restart. Any relative `output.sqlite.path` is now rewritten to live under the persistent `/config` on startup; values already set to an absolute path are left untouched. (https://github.com/tphakala/birdnet-go/discussions/3774)

View File

@@ -127,5 +127,5 @@ slug: birdnet-go-dev
udev: true
url: https://github.com/alexbelgium/hassio-addons
usb: true
version: "source-20260705-2"
version: "source-20260705-3"
video: true

View File

@@ -17,12 +17,22 @@ normalize_path() {
# Reject paths containing characters that would break the SQL/YAML literals
# we substitute into below. We deliberately allow only "safe" filename chars.
# Also reject ".." path segments so a relative path can't traverse outside
# the directory we prefix it with (e.g. "/config") when we rewrite it below.
validate_safe_path() {
local p="$1"
if [[ ! "$p" =~ ^[A-Za-z0-9._/-]+$ ]]; then
bashio::log.fatal "Refusing unsafe path: '$p' (only [A-Za-z0-9._/-] allowed)"
exit 1
fi
local segment
local IFS=/
for segment in $p; do
if [[ "$segment" == ".." ]]; then
bashio::log.fatal "Refusing unsafe path: '$p' (path traversal '..' is not allowed)"
exit 1
fi
done
}
if [ ! -f "$CONFIG_LOCATION" ]; then
@@ -149,8 +159,12 @@ bashio::log.info "Seeding default configuration values (only if missing)"
CURRENT_SQLITE_PATH="$(yq -r '.output.sqlite.path // ""' "$CONFIG_LOCATION")"
if [[ -n "$CURRENT_SQLITE_PATH" && "$CURRENT_SQLITE_PATH" != /* ]]; then
validate_safe_path "$CURRENT_SQLITE_PATH"
bashio::log.warning "output.sqlite.path ('$CURRENT_SQLITE_PATH') is relative and would not persist across restarts; rewriting to /config/$CURRENT_SQLITE_PATH"
yq -i -y ".output.sqlite.path = \"/config/${CURRENT_SQLITE_PATH}\"" "$CONFIG_LOCATION"
NEW_SQLITE_PATH="/config/${CURRENT_SQLITE_PATH}"
bashio::log.warning "output.sqlite.path ('$CURRENT_SQLITE_PATH') is relative and would not persist across restarts; rewriting to $NEW_SQLITE_PATH"
# SQLite does not create missing parent directories, so ensure one exists
# if the (validated, traversal-free) path includes a subdirectory.
mkdir -p "$(dirname "$NEW_SQLITE_PATH")"
yq -i -y ".output.sqlite.path = \"${NEW_SQLITE_PATH}\"" "$CONFIG_LOCATION"
fi
yq -i -y '.output.sqlite.path //= "/config/birdnet.db"' "$CONFIG_LOCATION"

View File

@@ -1,3 +1,6 @@
## nightly-20260615-3 (05-07-2026)
- Harden the `output.sqlite.path` rewrite added for the persistence fix: reject paths containing `..` traversal segments (shared `validate_safe_path` check), and create the destination's parent directory under `/config` when the relative path includes a subdirectory (e.g. `db/birdnet.db`), since SQLite does not create missing parent directories itself.
## nightly-20260615-2 (05-07-2026)
- Minor bugs fixed
- Fix detections/database not persisting across restarts on a fresh install: upstream's default `config.yaml` ships `output.sqlite.path: birdnet.db` (relative) explicitly, so the missing-only (`//=`) seeding introduced previously never rewrote it to an absolute path. A relative path resolves against the app's ephemeral working directory, so the database was silently recreated empty on every restart. Any relative `output.sqlite.path` is now rewritten to live under the persistent `/config` on startup; values already set to an absolute path are left untouched. (https://github.com/tphakala/birdnet-go/discussions/3774)

View File

@@ -128,4 +128,4 @@ slug: birdnet-go
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/birdnet-go
usb: true
version: "nightly-20260615-2"
version: "nightly-20260615-3"

View File

@@ -17,12 +17,22 @@ normalize_path() {
# Reject paths containing characters that would break the SQL/YAML literals
# we substitute into below. We deliberately allow only "safe" filename chars.
# Also reject ".." path segments so a relative path can't traverse outside
# the directory we prefix it with (e.g. "/config") when we rewrite it below.
validate_safe_path() {
local p="$1"
if [[ ! "$p" =~ ^[A-Za-z0-9._/-]+$ ]]; then
bashio::log.fatal "Refusing unsafe path: '$p' (only [A-Za-z0-9._/-] allowed)"
exit 1
fi
local segment
local IFS=/
for segment in $p; do
if [[ "$segment" == ".." ]]; then
bashio::log.fatal "Refusing unsafe path: '$p' (path traversal '..' is not allowed)"
exit 1
fi
done
}
if [ ! -f "$CONFIG_LOCATION" ]; then
@@ -145,8 +155,12 @@ bashio::log.info "Seeding default configuration values (only if missing)"
CURRENT_SQLITE_PATH="$(yq -r '.output.sqlite.path // ""' "$CONFIG_LOCATION")"
if [[ -n "$CURRENT_SQLITE_PATH" && "$CURRENT_SQLITE_PATH" != /* ]]; then
validate_safe_path "$CURRENT_SQLITE_PATH"
bashio::log.warning "output.sqlite.path ('$CURRENT_SQLITE_PATH') is relative and would not persist across restarts; rewriting to /config/$CURRENT_SQLITE_PATH"
yq -i -y ".output.sqlite.path = \"/config/${CURRENT_SQLITE_PATH}\"" "$CONFIG_LOCATION"
NEW_SQLITE_PATH="/config/${CURRENT_SQLITE_PATH}"
bashio::log.warning "output.sqlite.path ('$CURRENT_SQLITE_PATH') is relative and would not persist across restarts; rewriting to $NEW_SQLITE_PATH"
# SQLite does not create missing parent directories, so ensure one exists
# if the (validated, traversal-free) path includes a subdirectory.
mkdir -p "$(dirname "$NEW_SQLITE_PATH")"
yq -i -y ".output.sqlite.path = \"${NEW_SQLITE_PATH}\"" "$CONFIG_LOCATION"
fi
yq -i -y '.output.sqlite.path //= "/config/birdnet.db"' "$CONFIG_LOCATION"