Skip to content

FTL v6.6.1#2863

Merged
PromoFaux merged 70 commits into
masterfrom
development
Apr 24, 2026
Merged

FTL v6.6.1#2863
PromoFaux merged 70 commits into
masterfrom
development

Conversation

@PromoFaux

Copy link
Copy Markdown
Member

What's Changed

New Contributors

Full Changelog: v6.6...v6.6.1

darkexplosiveqwx and others added 30 commits February 21, 2026 22:07
Signed-off-by: darkexplosiveqwx <101737077+darkexplosiveqwx@users.noreply.github.com>
…config items as read-only

Signed-off-by: Dominik <dl6er@dl6er.de>
Move all API-related tests (HTTP endpoints, config validation, auth,
search, history, lists, Lua pages, OpenAPI spec validation) from BATS
shell tests to pytest. BATS retains DNS, regex, CLI, and system-level
tests. The auth test suite now removes the password at the end, leaving
no net state change.

Fix ResponseVerifyer to respect the OpenAPI "required" field: optional
properties absent from FTL's response are silently skipped instead of
flagged as errors. The required list is propagated through all recursive
calls (top-level objects, allOf, nested objects, array items).

Add a set_config() helper that changes FTL configuration via the API
instead of shelling out to the CLI. The API PATCH is synchronous, so no
log polling or sleeps are needed.

Add build.sh test-api target to run only the pytest API tests (skips
BATS and perf tests).

Test migration mapping (25 BATS tests removed, all covered in pytest):

| # | Removed BATS test | Pytest counterpart |
|---|---|---|
| 1 | HTTP server responds with JSON error 404 to unknown API path | TestHTTPErrors::test_api_404_returns_json |
| 2 | HTTP server responds with error 404 to path outside /admin | TestHTTPErrors::test_non_admin_path_returns_404 |
| 3 | Config validation working on the API (type-based checking) | TestConfigValidationAPIType (2 tests) |
| 4 | Config validation working on the API (validator-based checking) | TestConfigValidationAPIValidator (4 tests) |
| 5 | Changing a config option set forced by ENVVAR is not possible via the API | TestEnvvarProtectedConfig::test_api_rejects_envvar_override |
| 6 | API domain search: Non-existing domain | TestDomainSearch::test_nonexistent_domain |
| 7 | API domain search: antigravity.ftl | TestDomainSearch::test_antigravity_domain |
| 8 | API domain search: Internationalized/partially capital domain | TestDomainSearch::test_punycode_normalization |
| 9 | API history: Returns full 24 hours | TestHistory::test_history_returns_24h |
| 10 | API history/clients: Returns full 24 hours | TestHistory::test_history_clients_returns_24h |
| 11 | Check /api/lists?type=block | TestLists::test_block_lists_only |
| 12 | Check /api/lists?type=allow | TestLists::test_allow_lists_only |
| 13 | Check /api/lists without type parameter | TestLists::test_all_lists_includes_both_types |
| 14 | API: No UNKNOWN reply in API | TestQueries::test_no_unknown_reply |
| 15 | API: No UNKNOWN status in API | TestQueries::test_no_unknown_status |
| 16 | Lua server page outside /admin is not served by default | TestLuaServerPages::test_lua_page_outside_admin_not_served_by_default |
| 17 | Lua server page is generating proper backtrace | TestLuaServerPages::test_lua_page_generates_proper_backtrace |
| 18 | Lua server page outside of webhome is served without login | TestLuaServerPages::test_lua_page_outside_webhome_served_without_login |
| 19 | API validation (checkAPI.py) | TestEndpointCoverage (3 tests) + TestEndpointResponses + TestTeleporter |
| 20 | API authorization (without password): No login required | TestAuthWorkflow::test_01_no_password_means_session_valid |
| 21 | Create, set, and use application password | TestAuthWorkflow::test_02 + test_03 + test_04 |
| 22 | CLI password file is as expected | TestAuthWorkflow::test_04b_cli_password_file |
| 23 | API authorization: Setting password | TestAuthWorkflow::test_05_set_password |
| 24 | API authorization (with password): Incorrect password is rejected | TestAuthWorkflow::test_06_incorrect_password_rejected |
| 25 | API authorization (with password): Correct password is accepted | TestAuthWorkflow::test_07_correct_password_accepted |

New tests without a BATS predecessor:
- TestAuthWorkflow::test_08_rate_limiting_enforced
- TestAuthWorkflow::test_09_remove_password
- TestAuthWorkflow::test_10_no_password_after_removal

Final count: 192 BATS + 38 pytest = 230 total (was 216 BATS). No tests
lost, 14 net new.

Signed-off-by: Dominik <dl6er@dl6er.de>
Move all API-related tests (HTTP errors, config validation, auth,
search, history, lists, Lua pages, OpenAPI spec validation) from BATS
to pytest.  BATS retains DNS, regex, CLI, and system-level tests.

The test run order is now:
  1. BATS test_suite.bats (186 tests) — DNS, regex, CLI, config
  2. pytest test/api/ (38 tests) — API endpoints, OpenAPI, auth
  3. BATS test_final.bats (6 tests) — log validation, config
     rotation counts, FTL termination

Key changes:

- Fix ResponseVerifyer to respect the OpenAPI "required" field so
  optional properties absent from FTL's response are silently skipped
  instead of reported as errors (already committed in prior commit).

- Auth tests (test_z_auth.py) set and remove the password, leaving
  no net state change.  Password hashing (BALLOON-SHA256) is fully
  synchronous — no sleeps or retries needed after setting a password.

- Teleporter import triggers an internal FTL restart (exit code 22,
  gravity database reload).  The test waits for FTL to come back so
  subsequent auth tests have a working API.

- All API tests use a shared requests.Session (api_session fixture)
  that authenticates once via X-FTL-SID header.  No per-request
  _get_session_header() calls.

- Config changes in pytest use set_config() which PATCHes the API
  instead of shelling out to the CLI.  The API PATCH is synchronous
  so no log polling or sleeps are needed.

- Log validation (WARNING/ERROR/CRIT/DB) moved from test_suite.bats
  to test_final.bats so it runs after both BATS and pytest, catching
  messages from the entire run.

- gdb attachment is now opt-in (GDB=1) instead of always-on.

Files added:
  test/api/conftest.py       — shared fixtures (api_session, openapi, ftl)
  test/api/pytest.ini        — pytest configuration
  test/api/test_api.py       — HTTP errors, config validation, search,
                                history, lists, queries, Lua pages
  test/api/test_openapi.py   — OpenAPI spec validation, teleporter
  test/api/test_z_auth.py    — auth workflow (app password, login,
                                rate limiting, password removal)
  test/test_final.bats       — log validation, config rotation counts,
                                FTL termination

Files modified:
  build.sh                   — remove test-api target (pytest needs
                                BATS seeding data)
  test/run.sh                — run BATS then pytest then test_final,
                                gdb opt-in via GDB=1
  test/test_suite.bats       — remove API tests (moved to pytest),
                                remove log validation (moved to
                                test_final.bats), add CLI password
                                set/remove test with wait-for

Test migration (25 BATS tests removed, all covered in pytest):

| # | Removed BATS test | Pytest counterpart |
|---|---|---|
| 1 | HTTP server responds with JSON error 404 to unknown API path | TestHTTPErrors::test_api_404_returns_json |
| 2 | HTTP server responds with error 404 to path outside /admin | TestHTTPErrors::test_non_admin_path_returns_404 |
| 3 | Config validation working on the API (type-based checking) | TestConfigValidationAPIType (2 tests) |
| 4 | Config validation working on the API (validator-based checking) | TestConfigValidationAPIValidator (4 tests) |
| 5 | Changing a config option set forced by ENVVAR is not possible via the API | TestEnvvarProtectedConfig::test_api_rejects_envvar_override |
| 6 | API domain search: Non-existing domain | TestDomainSearch::test_nonexistent_domain |
| 7 | API domain search: antigravity.ftl | TestDomainSearch::test_antigravity_domain |
| 8 | API domain search: Internationalized/partially capital domain | TestDomainSearch::test_punycode_normalization |
| 9 | API history: Returns full 24 hours | TestHistory::test_history_returns_24h |
| 10 | API history/clients: Returns full 24 hours | TestHistory::test_history_clients_returns_24h |
| 11 | Check /api/lists?type=block | TestLists::test_block_lists_only |
| 12 | Check /api/lists?type=allow | TestLists::test_allow_lists_only |
| 13 | Check /api/lists without type parameter | TestLists::test_all_lists_includes_both_types |
| 14 | API: No UNKNOWN reply in API | TestQueries::test_no_unknown_reply |
| 15 | API: No UNKNOWN status in API | TestQueries::test_no_unknown_status |
| 16 | Lua server page outside /admin is not served by default | TestLuaServerPages::test_lua_page_outside_admin_not_served_by_default |
| 17 | Lua server page is generating proper backtrace | TestLuaServerPages::test_lua_page_generates_proper_backtrace |
| 18 | Lua server page outside of webhome is served without login | TestLuaServerPages::test_lua_page_outside_webhome_served_without_login |
| 19 | API validation (checkAPI.py) | TestEndpointCoverage (3) + TestEndpointResponses + TestTeleporter |
| 20 | API authorization (without password): No login required | TestAuthWorkflow::test_01_no_password_means_session_valid |
| 21 | Create, set, and use application password | TestAuthWorkflow::test_02 + test_03 + test_04 |
| 22 | CLI password file is as expected | TestAuthWorkflow::test_04b_cli_password_file |
| 23 | API authorization: Setting password | TestAuthWorkflow::test_05_set_password |
| 24 | API authorization: Incorrect password is rejected | TestAuthWorkflow::test_06_incorrect_password_rejected |
| 25 | API authorization: Correct password is accepted | TestAuthWorkflow::test_07_correct_password_accepted |

New tests without a BATS predecessor:
- TestAuthWorkflow::test_08_rate_limiting_enforced
- TestAuthWorkflow::test_09_remove_password
- TestAuthWorkflow::test_10_no_password_after_removal

Log validation tests moved from test_suite.bats to test_final.bats:
- No WARNING messages in FTL.log
- No ERROR messages in FTL.log
- No CRIT messages in FTL.log
- No "DB not available" messages in FTL.log
- Expected number of config file rotations
- FTL terminates with message

Final count: 186 BATS + 38 pytest + 6 final BATS = 230 total
(was 216 BATS). No tests lost, 14 net new.

Signed-off-by: Dominik <dl6er@dl6er.de>
Add the pytest test files and final BATS suite that were described
in the previous commit but not yet included.

New files:
  test/api/conftest.py     — shared fixtures (api_session, openapi, ftl)
  test/api/pytest.ini      — pytest configuration
  test/api/test_api.py     — HTTP errors, config validation, search,
                              history, lists, queries, Lua pages
  test/api/test_openapi.py — OpenAPI spec validation, teleporter
  test/api/test_z_auth.py  — auth workflow (app password, login,
                              rate limiting, password removal)
  test/test_final.bats     — log validation, config rotation counts,
                              FTL termination

Signed-off-by: Dominik <dl6er@dl6er.de>
…TLenv

Add pytest tests for all previously untested GET API endpoints:
dns/blocking, domains (all type/kind combinations and single lookup),
groups, stats/summary, stats/top_domains, stats/top_clients,
stats/upstreams, stats/query_types, stats/recent_blocked,
stats/database (error handling), dhcp/leases, endpoints, info/ftl,
info/login, info/version, info/messages, info/client, info/database,
info/system, network/devices, network/interfaces, logs (dnsmasq, ftl,
webserver), and padd.

All assertions use exact expected values derived from the deterministic
BATS DNS query seeding (137 total queries, 49 blocked, 47 forwarded,
41 cached, 11 active clients, 8 gravity domains).  On failure, the
full JSON response is dumped to /tmp/ftl_test_*.json for easy
inspection.

Fix double-free bug in printFTLenv() (src/config/env.c): when
printFTLenv() was called more than once (e.g. after config reload
triggered by the CLI password test), it would free item->error a
second time because neither the pointer nor the error_allocated flag
were reset after the first free.  This produced "Trying to free NULL
pointer in printFTLenv()" warnings.  Fix: set item->error = NULL and
item->error_allocated = false after freeing.

Files modified:
  src/config/env.c      — reset error/error_allocated after free
  test/api/test_api.py  — add 34 new endpoint tests (22 -> 56 total)

Signed-off-by: Dominik <dl6er@dl6er.de>
… on this platform.

Signed-off-by: Dominik <dl6er@dl6er.de>
Co-authored-by: Adam Warner <me@adamwarner.co.uk>
Signed-off-by: Dominik <DL6ER@users.noreply.github.com>
test_openapi.py: Store auth_method per endpoint alongside errors so
the assertion message shows the correct auth method for each failing
endpoint instead of the last loop iteration's value.

run.sh: Update comment to reflect that BATS no longer terminates FTL
(termination was moved to test_final.bats).

Signed-off-by: Dominik <dl6er@dl6er.de>
…ilure

test_network_devices expects ip-127.0.0.1 in the network devices list,
but this mock-hwaddr entry is only created when parse_neighbor_cache()
runs in the database thread. That function fires after a DBinterval
(60s) elapses. On 32-bit emulated builds, the timing race is lost and
the entry doesn't exist yet when the test runs.

Send RT signal 5 (PARSE_NEIGHBOR_CACHE) to FTL before starting pytest
so the network table is populated regardless of elapsed time.

Signed-off-by: Dominik <dl6er@dl6er.de>
Replace SKIP_PERF_TEST=1 (opt-out) with RUN_PERF_TEST=1 (opt-in).
Standard `./build.sh test` no longer runs the slow --perf suite;
use `./build.sh test-perf` to include it.

Increase CI retry timeouts for QEMU-emulated builds (especially
RISC-V): build 15→30 min, test 10→20 min.

Signed-off-by: Dominik <dl6er@dl6er.de>
The test schema had drifted from the real /etc/pihole/gravity.db:
- domainlist/adlist used single-column UNIQUE instead of composite
  UNIQUE(domain,type) / UNIQUE(address,type), causing silent SQL
  failures on INSERT ... ON CONFLICT
- adlist column order had `type` in wrong position
- client table had `NOL NULL` typo (should be `NOT NULL`)
- *_by_group tables were missing ON DELETE CASCADE and WITHOUT ROWID
- info table was missing WITHOUT ROWID
- vw_gravity had extra `AND adlist.type = 0` filter not in production
- vw_adlist was missing `type` column in SELECT
- domainlist views had ORDER BY clauses not present in production
- Three indexes were missing (idx_adlist_by_group_gid,
  idx_domainlist_by_group_gid, idx_gravity)

Signed-off-by: Dominik <dl6er@dl6er.de>
api_list_write() unconditionally returned 200/201 even when
gravityDB_addToTable() failed for every item (e.g., SQL prepare
errors). The error details were captured in the processed.errors
array but the HTTP status code indicated success.

Now returns 400 with a database_error when no items were
successfully added. Partial failures in batch operations still
return 200 with the processed object detailing which items
succeeded and which failed.

Also adds the database_error example to the PUT 400 response
specs for groups, clients, and lists (domains already had it).

Signed-off-by: Dominik <dl6er@dl6er.de>
Add ~80 new pytest API integration tests covering previously untested
endpoints and HTTP methods:

- DELETE 204/404 for groups, domains, clients, lists, config array
  items, network devices, and info messages
- PUT create/replace round-trips for groups, domains, clients, lists
- PUT error cases (missing body, invalid domain type)
- Batch delete (POST :batchDelete) for groups, domains, clients, lists
- DNS blocking toggle (POST disable + re-enable)
- Auth session logout (DELETE /api/auth) and delete-by-ID
- GET endpoints: clients, config, network gateway/routes, info
  host/sensors/metrics, query filters (domain, client_ip, upstream,
  blocklist pseudo-upstream), query suggestions, query cursor
  pagination, stats database with time ranges, history database
  with time ranges
- Search with partial matching
- TOTP credential suggestion (GET /api/auth/totp)
- Config PATCH round-trip (bool and integer, change + verify + restore)
- NTP server protocol-level test (UDP NTPv4 request/response)
- Update test_final.bats known-warning patterns and config write counts

Signed-off-by: Dominik <dl6er@dl6er.de>
When addr2line was absent from the runtime system, popen() succeeded
but produced no output, leaving func as an empty string. The check
`strcmp(func, "??")` did not match "", so the code fell through to
the success branch and logged only bare frame numbers (#0, #1, ...)
with no addresses or symbols.

Fix the condition to also check for empty func, so the dladdr() and
/proc/self/maps fallbacks execute correctly. Additionally, when any
frame cannot be resolved, print the addr2line commands the user can
run manually after installing binutils in case the tool was missing.

Signed-off-by: Dominik <dl6er@dl6er.de>
Sync master back into development
Signed-off-by: Dominik <dl6er@dl6er.de>
Add new `GET /api/config/_properties` endpoint
Bumps the npm-dependencies group with 1 update: [openapi-examples-validator](https://github.com/codekie/openapi-examples-validator).


Updates `openapi-examples-validator` from 6.0.3 to 7.0.0
- [Changelog](https://github.com/codekie/openapi-examples-validator/blob/main/CHANGELOG.md)
- [Commits](codekie/openapi-examples-validator@v6.0.3...v7.0.0)

---
updated-dependencies:
- dependency-name: openapi-examples-validator
  dependency-version: 7.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Dominik <dl6er@dl6er.de>
…in the latest base images

Signed-off-by: Dominik <dl6er@dl6er.de>
Two API handlers stored direct pointers into the shared memory strings
buffer (via cJSON_CreateStringReference), released the SHM lock, and
then serialized the JSON - dereferencing those pointers after the lock
was no longer held.

Between unlock and serialization, another thread (DNS processing, GC,
or another civetweb worker) can call realloc_shm() which invokes
mremap(..., MREMAP_MAYMOVE), moving the entire strings buffer to a new
virtual address and leaving dangling pointers in the JSON tree.

This caused SIGSEGV crashes in civetweb-worker threads, always while the
dashboard was open. Symptoms included NULL dereferences (unmapped old
region) and faulting addresses containing domain string fragments (stale
data at the former buffer location).

Affected handlers:
- api_history_clients(): client_name from getstr() stored as reference,
  lock released before JSON_SEND_OBJECT serialization
- api_stats_recent_blocked(): domain from getDomainString() stored as
  reference, lock released before serialization

Fix: replace JSON_REF_STR_IN_OBJECT/ARRAY with JSON_COPY_STR_TO_OBJECT/
ARRAY, which copy the string into cJSON-owned heap memory while the lock
is still held. Also correct the misleading comment in history.c claiming
shared memory strings are "idempotent".

Closes #2786

Signed-off-by: Dominik <dl6er@dl6er.de>
When CAP_CHOWN is missing (e.g. in Docker with cap_drop), init_FTL_log()
could fail to open the log file and set config.files.log.ftl.v.s = NULL.
This NULL value was then persisted as an empty string when the TOML
config was rewritten shortly after during readFTLconf(). The empty path
remained even after restoring CAP_CHOWN, permanently breaking file
logging.

Use a separate static flag to track log file availability instead of
clobbering the config value, so the configured path survives fopen
failures and is correctly written back to pihole.toml.

Fixes: #2827

Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
pi_hole_extra_headers is a global char[1024] buffer written by API
handlers and read/cleared by civetweb's send_additional_header(), but
civetweb runs up to 50 worker threads concurrently. When multiple
threads handle authenticated requests in parallel, one thread can
overwrite or clear another's header data, causing wrong Set-Cookie
headers to be sent to wrong clients or cookies to be dropped entirely.

Make pi_hole_extra_headers _Thread_local so each worker thread gets its
own buffer. This is safe because civetweb handles each request entirely
within a single thread.

The auth_data session array has a similar race: concurrent threads
read/modify sessions without synchronization. Add a pthread mutex
protecting all auth_data access, using AUTOLOCK/AUTOUNLOCK macros based
on __attribute__((cleanup)) for RAII-style auto-unlock — ensuring the
mutex is released on every exit path, including hidden returns inside
JSON macros that do `return 500` on allocation failure.

Change api->session from a pointer into auth_data to an embedded struct
copy so downstream API handlers read from a per-request snapshot rather
than shared state. Use JSON_COPY_STR_TO_OBJECT for auth_data strings
so the JSON tree owns its own copies after the lock is released.

Fixes: #2824

Signed-off-by: Dominik <dl6er@dl6er.de>
Add test/api/test_s_auth_stress.py exercising the auth subsystem under
concurrent load (12 threads, kept below max_sessions=16). Sessions are
created sequentially (respecting FTL's 3-attempts/s rate limit with
retry-on-429 backoff), then concurrency targets the thread-safety
surfaces:

- Parallel session validation (concurrent GET /api/auth with SIDs)
- Concurrent logout with cross-session isolation checks (logout half,
  verify other half survives)
- Mixed session check/logout operations from a shared session pool
- Concurrent wrong-password rejection (verifies no SIGSEGV; runs last
  since it intentionally triggers rate-limiting)

Every test cleans up its sessions to avoid exhausting max_sessions
across test boundaries. Bump expected config rotation count in
test_final.bats from 14 to 16 for the stress test's password
set/remove cycle.

Also add test/libs/ to .gitignore per review feedback — the directory is
populated at test time by test/run.sh cloning bats-core.

See alse: #2835

Signed-off-by: Dominik <dl6er@dl6er.de>
Copilot AI and others added 24 commits April 12, 2026 08:01
find_device_by_hwaddr() returned DB_NODATA without querying the
database when resolver.macNames was false. This caused all three
callers (parse_neighbor_cache, add_FTL_clients_to_network_table,
add_local_interfaces_to_network_table) to treat every device as new
and attempt INSERT on each periodic run, hitting the UNIQUE constraint
on network.hwaddr every ~60 seconds.

The macNames setting controls name resolution from MAC addresses, not
device tracking — the early return was conflating the two. The other
two uses of the flag (getNameFromMAC and the gravity-db MAC lookup)
correctly gate name resolution only and are unaffected.

Closes #2844

Signed-off-by: Dominik <dl6er@dl6er.de>
Improve shutdown diagnostics to identify SIGTERM source
Resolve empty backtraces when addr2line is not installed
…. When multiple civetweb workers called /api/search/{domain} concurrently, each call to gravityDB_readTable() would overwrite the shared read_stmt via sqlite3_prepare_v2(), corrupting other threads'in-progress queries. This caused sqlite3_column_name() to dereference invalid memory -> SIGSEGV.

fix: replaced the shared static read_stmt with a caller-owned sqlite3_stmt* passed through the function signatures. Each API request now gets its own prepared statement, eliminating the race condition.

Signed-off-by: Dominik <dl6er@dl6er.de>
Two API handlers created cJSON string references to config struct strings (via cJSON_CreateStringReference / JSON_REF_STR_IN_OBJECT) that could be freed by a concurrent config change.

When another civetweb worker handles a PATCH /api/config request, apply_config() replaces the global config struct and frees the old one - including all its dynamically allocated strings. Any in-flight GET request that still holds a reference into the old struct then dereferences freed memory during JSON serialization.

Affected:
- config.c:addJSONConfValue(): CONF_STRING/CONF_STRING_ALLOCATED values returned as cJSON_CreateStringReference(val->s)
- 2fa.c:generateTOTP(): config.webserver.domain.v.s stored as JSON_REF_STR_IN_OBJECT

Fix: use cJSON_CreateString / JSON_COPY_STR_TO_OBJECT to copy the string into cJSON-owned heap memory before the config can change.

Signed-off-by: Dominik <dl6er@dl6er.de>
Tests: auto-detect upstream DNSSEC state for query-count assertions
Signed-off-by: Dominik <dl6er@dl6er.de>
Bumps the npm-dependencies group with 1 update: [openapi-examples-validator](https://github.com/codekie/openapi-examples-validator).


Updates `openapi-examples-validator` from 7.0.0 to 7.1.0
- [Changelog](https://github.com/codekie/openapi-examples-validator/blob/main/CHANGELOG.md)
- [Commits](codekie/openapi-examples-validator@v7.0.0...v7.1.0)

---
updated-dependencies:
- dependency-name: openapi-examples-validator
  dependency-version: 7.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
…pdates

Bumps the github_action-dependencies group with 3 updates in the / directory: [actions/upload-artifact](https://github.com/actions/upload-artifact), [softprops/action-gh-release](https://github.com/softprops/action-gh-release) and [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request).


Updates `actions/upload-artifact` from 7.0.0 to 7.0.1
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@bbbca2d...043fb46)

Updates `softprops/action-gh-release` from 2.6.1 to 3.0.0
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](softprops/action-gh-release@153bb8e...b430933)

Updates `peter-evans/create-pull-request` from 8.1.0 to 8.1.1
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](peter-evans/create-pull-request@c0f553f...5f6978f)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github_action-dependencies
- dependency-name: peter-evans/create-pull-request
  dependency-version: 8.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
…opment-github_action-dependencies-3b7f5c993f

Bump the github_action-dependencies group across 1 directory with 3 updates
…ment-npm-dependencies-201d9e89b8

Bump openapi-examples-validator from 7.0.0 to 7.1.0 in the npm-dependencies group across 1 directory
Improve thread-safety for concurrent API requests
Don't skip device lookup when resolver.macNames is disabled
Compiling Pi-hole FTL w/o optimization (release type "DEBUG") results in
the following error during linking:

'''
x86_64-buildroot-linux-uclibc/bin/ld: tools/CMakeFiles/tools.dir/dhcp-discover.c.o: in function `create_dhcp_socket':
/home/data/buildroot.experimental/build/pihole-ftl-6.6/src/tools/dhcp-discover.c:88:(.text+0xac): undefined reference to `start_lock'
x86_64-buildroot-linux-uclibc/bin/ld: /home/data/buildroot.experimental/build/pihole-ftl-6.6/src/tools/dhcp-discover.c:90:(.text+0xe9): undefined reference to `end_lock'
[...]
x86_64-buildroot-linux-uclibc/bin/ld: tools/CMakeFiles/tools.dir/dhcpv6-discover.c.o: in function `recv_adv':
/home/data/buildroot.experimental/build/pihole-ftl-6.6/src/tools/dhcpv6-discover.c:706:(.text+0x137f): undefined reference to `start_lock'
x86_64-buildroot-linux-uclibc/bin/ld: /home/data/buildroot.experimental/build/pihole-ftl-6.6/src/tools/dhcpv6-discover.c:709:(.text+0x13b1): undefined reference to `end_lock'
[...]
'''

When generating code without optimization, gcc ignores the inline
directive. Code for this function is generated once, but not marked
as global (because the extern keyword is missing), which results
in the observed linker error for all other compilation units.

Change the function definition to static inline, which will force the
compiler to include a static copy of the function in every compilation
unit, if the function is not inlined.

Signed-off-by: Andreas Ziegler <15275159+aeolio@users.noreply.github.com>
Fix linker error when compiling w/o optimization
Bumps the github_action-dependencies group with 1 update in the / directory: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 4.35.1 to 4.35.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@c10b806...95e58e9)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
…opment-github_action-dependencies-f3e34333ea

Bump github/codeql-action from 4.35.1 to 4.35.2 in the github_action-dependencies group across 1 directory
…failure

Preserve log file path config when fopen fails
The PID file path was previously user-configurable via files.pid in
pihole.toml. Service hook scripts executed as root read this value
without validation and used it in privileged file operations, enabling
local privilege escalation by a pihole user with direct write access
to pihole.toml.

Remove files.pid from the config system entirely and replace all
usages with the compile-time constant FTL_PID_FILE ("/run/pihole-FTL.pid")
defined in config.h. The PID file path has no good reason to be
user-configurable.

See: GHSA-6w8x-p785-6pm4

Signed-off-by: yubiuser <github@yubiuser.dev>
Co-authored-by: Adam Warner <me@adamwarner.co.uk>
Signed-off-by: yubiuser <github@yubiuser.dev>
@PromoFaux PromoFaux requested a review from a team as a code owner April 24, 2026 20:55
Hardcode PID file location to /run/pihole-FTL.pid
Update DNS interface validation to prevent newlines in `dns.interface`
@PromoFaux PromoFaux merged commit e1b4c1d into master Apr 24, 2026
19 checks passed
@dschaper dschaper requested a review from rdwebdesign April 24, 2026 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants