Ctrl K
ComunidadGuías…

CHANGELOG.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Changelog

All notable changes to HydraDNS are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project aims
to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) from v0.1.0.

## [Unreleased]

## [0.1.1] - 2026-09-26

The v0.1.0 release was withdrawn; v0.1.1 is the first release under Apache-2.0.

### Added
- `apps/cli/server.json` and a manual **Publish to MCP Registry** workflow that lists the MCP
  server in the official registry using GitHub OIDC.

### Changed
- Relicensed from GPL-3.0 to the Apache License 2.0.
- `apps/core` now builds with Go 1.26 (was 1.24), required by `gin-contrib/cors` 1.7.9 and
  `golang.org/x/crypto` 0.56; its build images are `golang:1.26-alpine`. The CLI and scanner stay on Go 1.25.
- Dependency updates: `grpc` 1.84.0, `protobuf` 1.36.12, `gin-contrib/cors` 1.7.9; dashboard `react` and
  `react-dom` 19.3.0, `tailwind-merge` 3.7.0, `@radix-ui/react-dropdown-menu` 2.1.24, `@tabler/icons-react`
  3.48.0, `@testing-library/react` 16.3.3; CI actions `checkout` v7, `setup-node` v7, `docker/login-action` v4,
  `docker/metadata-action` v6.
- Dependency updates: `miekg/dns` 1.1.73, `gin` 1.12.0, `gorm` 1.31.2, `logrus` 1.10.2; dashboard
  `@radix-ui/react-tabs` 1.1.21, `react-popover` 1.1.23, `react-tooltip` 1.2.16, `@testing-library/jest-dom` 7.0.1.
- The three Go modules now declare paths under this repository: `github.com/hydradns/hydradns/apps/core`,
  `github.com/hydradns/hydradns/apps/cli` and `github.com/hydradns/hydradns/apps/scanner` (they used the
  archived per-service repository paths). `go install github.com/hydradns/hydradns/apps/cli@latest`
  works once a release is tagged after this change, and pkg.go.dev can index the modules. Docker
  users are unaffected. Installing a specific version with `go install` needs a tag prefixed with
  the module directory, for example `apps/cli/v0.2.0`; see `docs/releasing.md`.

## [0.1.0] - 2026-09-20

### Added
- Added a public demo mode (`HYDRA_DEMO_MODE=true`): a read-only demo with a fixed
  password, seeded synthetic policies/blocklists/query-logs, and masked client IPs, meant
  for a public clickable demo rather than a normal self-hosted install. It refuses to
  start against a database that already has real users, and ships with its own
  `demo/docker-compose.demo.yml` stack and `demo/README.md`.
- `hydra setup` is a first-boot CLI command that creates the admin account through the
  setup endpoint and stores the token, the same way `hydra login` does. It supports a
  no-echo password prompt or `--password-stdin`; there is deliberately no `--password`
  flag.
- Added API routes backing dashboard features that previously had no server side:
  `GET /analytics/logs` (server-side paging and filtering for the Logs page, page size
  capped at 200), `GET /analytics/bypass` (DoH/DoT bootstrap-detection attempts),
  `PUT /policies/:id`, and `PATCH /blocklists/:id`. The latter two carry the same
  validation, role checks, and audit events as create/delete.
- Added a legacy-database upgrade path: on startup, if `hydradns.db` doesn't exist but a
  pre-rename `phantomdns.db` does, the control plane opens that file instead of starting
  empty. This is a one-time upgrade fallback, not the default for new installs.
- The DoH-bypass-attempts panel is now hidden on the dashboard by default; set
  `NEXT_PUBLIC_SHOW_BYPASS_PANEL=true` at build time to show it (e.g. for
  technical/internal deployments).
- Added `apps/cli/Dockerfile` and published `ghcr.io/hydradns/hydra-cli`, a small
  non-root image whose default command runs `hydra mcp` (stdio JSON-RPC), so the CLI's
  MCP server can run via `docker run -i --rm -e HYDRA_API_URL -e HYDRA_TOKEN
  ghcr.io/hydradns/hydra-cli` without a local Go toolchain. See `docs/mcp.md` for the
  full tool list, roles, and per-platform container networking.
- Added `docs/mcp.md`, public documentation for the `hydra mcp` server: the 14 tools,
  `MCP_ROLE` permission scopes, security notes, and verified client configuration for
  Claude Desktop, Claude Code, Cursor, VS Code, and the Gemini CLI, for both the local
  binary and the container image.
- Added blocklist parsers for the `domains` (one host per line) and `adblock` (EasyList
  `||domain^`) formats, alongside the existing `hosts` format.
- Added `docs/releasing.md`, a release runbook for cutting tags: pre-flight checks, what
  `release.yml` publishes, GHCR visibility/verification, and rollback.
- Added an automatic same-host CORS rule on the control plane API
  (`apps/core/cmd/controlplane/middlewares/cors.go`): a request is allowed when its
  `Origin` hostname matches the `Host` header it was reached on and that hostname is an
  IP literal or `localhost`, e.g. so a dashboard opened at `http://192.168.1.53:3000` can
  call the API at `http://192.168.1.53:8080` with no `CORS_ORIGINS` edit. Named hosts are
  excluded on purpose, since a DNS-rebinding attack could make `Origin` and `Host` agree
  on an attacker-controlled name and so still need an explicit `CORS_ORIGINS` entry;
  disable the whole rule with `CORS_ALLOW_SAME_HOST=false`.

### Changed
- Consolidated the five service repositories into a single monorepo under `apps/`. A
  plain `git clone` now checks out the whole stack.
- Renamed the project internals from PhantomDNS to HydraDNS (Go module paths, protobuf
  package, environment variables, and the on-disk database).
- `docker-compose.yml` now declares `image: ghcr.io/hydradns/<service>:${HYDRA_VERSION:-latest}`
  alongside the existing `build:` section for `core` and `ui`, so `docker compose up -d`
  pulls a published release instead of compiling from source (before the first release,
  or offline, Compose still falls back to building locally). Pin a version via
  `HYDRA_VERSION` in `.env`.
- The dashboard now derives the control-plane API's address from the page's own URL at
  runtime (same protocol and hostname, port 8080) instead of relying only on the value
  baked in at build time, so the published `ghcr.io/hydradns/ui` image works over a LAN IP
  without a rebuild. `NEXT_PUBLIC_API_URL` is now needed only to override this, e.g. for a
  reverse proxy or an API on a different host/port than the dashboard.

### Fixed
- `hydra update` could never find an update once the CLI moved into this monorepo, since
  its release feed still pointed at the archived `hydradns/hydra-cli` repo; it now points
  at `hydradns/hydradns`, where `release.yml`'s `release-cli` job publishes the CLI
  binaries. `hydra update` also refuses to install a binary without a matching SHA-256
  checksum, so a new `release-cli-checksums` job now generates and uploads
  `checksums.txt` for each release.
- `docker-compose.yml` shipped `CORS_ORIGINS=*` for the `core` service, which combined
  with the control plane's `AllowCredentials: true` disabled CORS protection on the API
  entirely. The default is now `CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:3000}` plus
  the automatic same-host rule above, matching `.env.example`; an explicit `CORS_ORIGINS`
  entry is only needed for a named host, not ordinary LAN-by-IP access.
- `demo/install.tape` incorrectly commented that a blocked `dig` query returns `REFUSED`;
  the shipped default (`BLOCK_RESPONSE=zero`) actually returns `A 0.0.0.0`. It also
  assumed a prebuilt `./apps/cli/hydra` binary and an already-authenticated CLI, neither
  of which exist on a fresh clone before the first tagged release, so it's rewritten to
  build the CLI and mint a setup token first.
- `docs/pi-deployment.md` repeated the same `REFUSED` claim in its DNS verification and
  secondary-DNS-fallback sections, and its "Dashboard not accessible from LAN" section
  documented `CORS_ORIGINS` and `NEXT_PUBLIC_API_URL` as manual steps for LAN-by-IP
  access. Both are now automatic (see the same-host CORS rule and the dashboard's runtime
  API-URL derivation above), so that section now covers only the two cases that still
  need a manual step: a named host, and a dashboard served over HTTPS.
- The in-memory blocklist used to be built from every stored entry with no check on the
  source's enabled flag, so disabling a blocklist did not stop it from blocking. It's now
  built from enabled sources only, and the dataplane polls a cheap signature of the
  blocklist tables (`BLOCKLIST_POLL_INTERVAL`, default 5s; `0` disables) and rebuilds the
  in-memory set when it changes, so add/toggle/delete/finished-download propagate in
  about 5s instead of waiting for the 6-hour `BLOCKLIST_UPDATE_INTERVAL` cycle, the same
  speed policy edits already had.
- Query-log rows were stored with the client's ephemeral port attached (`ip:port`)
  instead of the bare IP; the client-IP filter and anonymization both now operate on the
  address alone, and legacy rows with a port still match the filter precisely.
- CLI release binaries built without the version stamped in, so every release reported
  the hardcoded fallback `1.0.0` regardless of the actual tag, and `hydra update` could
  never see a release as newer than what was already installed. `release.yml` now passes
  the tag version via `-X` at build time.
- The default `apps/core/configs/policies.json` seed policy blocked `apple.com`, breaking
  iCloud, the App Store, and iMessage for a first-time user on Apple devices. It's
  removed: the seed now ships only `block-ads` and `block-malware` example policies, and
  a test asserts the shipped policy file never blocks a critical platform domain again.
- The blocklist fetch pipeline is now single-flighted with a bounded retry, and a
  source's entries are fully replaced (not appended) on every new snapshot, so a domain
  removed upstream actually stops being blocked and a URL edit takes effect once the next
  download completes. Only the last 10 snapshot metadata rows are kept per source (this
  caps metadata only, not which domains are blocked), and if an in-memory rebuild fails
  it keeps its previous, still-enforced contents and retries on the next
  `BLOCKLIST_POLL_INTERVAL` tick.
- The per-IP rate limiter's eviction path could scan up to its full entry cap under one
  global lock when the tracked-IP map was full and no sampled entry had expired, letting
  an attacker with a large address pool (e.g. an IPv6 /64) hold that lock for an
  O(maxEntries) scan on every request from a fresh IP. It now inspects a bounded sample
  (64 entries) and resets the whole map rather than continuing the scan.
- `HYDRA_DEMO_MODE=true` now also refuses to start against a database that has query-log
  rows but no users. Previously the "zero users" check alone could let it seed the demo
  user over a pre-RBAC volume (or one mid-migration), and the periodic demo-data refresh
  loop would then delete that real history on its first tick.
- `GET /analytics/logs` now caps `page * page_size` at 100,000 (400 if exceeded) and its
  row count at the same figure, returning `total_capped: true` in the response when the
  count was capped. Previously an arbitrarily large `page` forced a full linear OFFSET
  scan, and the count for an unindexed filter (e.g. `suspicious`) was a full table scan
  on every page load.
- Policy `action` is now validated (`BLOCK`/`ALLOW`/`REDIRECT`, case-insensitive) on both
  create and update, and `REDIRECT` requires a valid `redirect_ip` on both. Previously
  only create checked this, so an update could silently turn a policy into a no-op with
  an unrecognized action string.
- `blocklist_entries.source_id` is now indexed. `DeleteSource` and the dashboard's
  per-source domain count were doing an unindexed scan of the whole table.
- SQLite's `busy_timeout` is now set to 30 seconds. The combined `core` container starts
  the controlplane and dataplane processes together, and both run database migrations
  against the same single-writer SQLite file on startup; without a busy timeout, one
  process's schema migration could make the other's migration attempt fail outright with
  `SQLITE_BUSY` instead of waiting for it to finish. See the upgrade note below.
- The dataplane's blocklist signature poll loop now has its stop function wired into
  shutdown instead of discarded, so `SIGINT`/`SIGTERM` stops that background goroutine
  cleanly.
- MCP tool read-only classification is now an explicit per-tool flag instead of guessed
  from a `get_`/`list_` naming prefix. `explain_anomaly` and `compare_to_last_month` only
  read data but don't match that prefix, so the old heuristic classified them as
  mutating, blocking the `reporter` MCP role from calling them and marking them
  confirmation-required for no reason.
- `docker-compose.yml` only passed
  `HYDRA_CONFIG`/`HYDRA_DB`/`HYDRA_POLICIES`/`CORS_ORIGINS`/`CORS_ALLOW_SAME_HOST` into
  the `core` container, so every other documented runtime setting in `.env.example`
  (`BLOCK_RESPONSE`, `BLOCKLIST_UPDATE_INTERVAL`, `BLOCKLIST_POLL_INTERVAL`,
  `QUERY_LOG_RETENTION_DAYS`, `QUERY_LOG_MAX_ROWS`, `QUERY_LOG_CLEANUP_INTERVAL`,
  `HYDRA_ANONYMIZE_CLIENT_IPS`, `HYDRA_ANON_SECRET`, `TRUSTED_PROXIES`) had no effect no
  matter what you put in `.env`. All nine are now forwarded, each with a default
  matching the code's own default. `DNS_LISTEN_ADDR` and `HYDRA_DEMO_MODE` are
  deliberately still not forwarded: the DNS port mapping is fixed so overriding the
  internal bind address would only break it, and demo mode has its own compose file
  (`demo/docker-compose.demo.yml`) so a stray value here can't turn a real install into a
  demo.

### Security
- Blocklist downloads refuse non-http(s) URLs and loopback, link-local (cloud metadata),
  unspecified and multicast targets, checked at connect time on every redirect hop.
  Redirects are capped at 5 and a download at 128 MiB. Private LAN hosts remain allowed.
- Added per-IP rate limiting on `POST /auth/login` and `POST /auth/setup` (fixed window,
  10 attempts / 5 minutes by default, shared across both endpoints), plus
  `TRUSTED_PROXIES` (comma-separated CIDRs/IPs) so the control plane only honors
  `X-Forwarded-For` from a reverse proxy you explicitly trust; otherwise `c.ClientIP()`
  always resolves to the real socket address. This also makes the audit log's recorded
  client IP trustworthy: previously Gin trusted every proxy by default, letting any
  caller spoof its own client IP.
- Opt-in client-IP pseudonymisation: `HYDRA_ANONYMIZE_CLIENT_IPS` (default `false`)
  hashes a client's IP with HMAC-SHA256 before it's written to the query log, using a
  per-install secret generated on first boot and persisted next to the database
  (`HYDRA_ANON_SECRET` overrides it). This is pseudonymisation, not anonymisation: anyone
  holding both the database and the secret file can brute-force the small IPv4 address
  space back to the original addresses. Previously this config key was parsed but never
  wired to anything, so enabling it had no effect.
- `POST /auth/login` and `POST /auth/setup` now only count a failed (4xx) attempt against
  the per-IP rate limit; a successful login and a 5xx (the server's own fault) no longer
  consume budget. Previously every call counted identically, so a legitimate admin
  re-authenticating from several devices behind one shared/NAT IP could lock themselves
  out purely by logging in successfully. The limit is relaxed to 100 attempts / 5 minutes
  when `HYDRA_DEMO_MODE=true`, since the demo password is fixed and publicly documented.
- Boolean environment variables (`HYDRA_DEMO_MODE`, `CORS_ALLOW_SAME_HOST`) now go
  through one shared parser: `true`/`1`/`yes`/`on` and `false`/`0`/`no`/`off`,
  case-insensitive and whitespace-trimmed. An unrecognized value now fails startup with
  an error naming the variable and value, instead of silently keeping the default;
  previously a near-miss like `"1"`, `"yes"`, or a trailing space from a Docker env_file
  line left `HYDRA_DEMO_MODE` silently off, and any value other than the literal
  `"false"` left `CORS_ALLOW_SAME_HOST` silently on.
- `CORS_ORIGINS` entries are now trimmed and validated (must be `*` or a bare
  `http(s)://host` with no path) before reaching the CORS library, which otherwise
  panicked at startup on the first malformed entry with no indication of which one or
  why. An invalid entry now fails startup with a clear message naming `CORS_ORIGINS` and
  the offending entry.
- `CreateBlocklist` (and the setup wizard's optional blocklist bootstrap) now validate
  the source URL scheme the same way `UpdateBlocklist` already did. Previously an
  operator-role user could add a blocklist source pointing at an internal address (e.g. a
  cloud metadata endpoint or a LAN admin page) through create or setup, just not through
  update.
- `GET /analytics/logs`'s `client=` filter is now rejected outright (400) in demo mode:
  an exact-match filter against the unmasked stored rows would otherwise let a public
  demo visitor use the filter as an oracle to recover the octet masked in the response.
- The anonymization secret generator now fails startup, rather than silently falling back
  to a shared hardcoded key, if `crypto/rand` cannot produce entropy. That fallback would
  have made every affected install share the same HMAC key, defeating anonymization for
  all of them at once.
- The CLI's token file (`~/.hydra/token`) is now written atomically (temp file, fsync,
  rename) with its directory forced to `0700` and the file to `0600` on every write, and
  a symlinked directory or token path is refused rather than followed.
- The CLI now prints a one-line stderr warning when its configured API URL is plain
  `http://` to a non-local address, since the login password and bearer token would
  otherwise cross the network in cleartext with no indication.

### Upgrade notes
- This release adds two database indexes (`dns_queries.action`,
  `blocklist_entries.source_id`) and a `busy_timeout` setting; both the controlplane and
  dataplane processes run migrations at startup. On an existing install with a large
  `dns_queries` table (SD-card storage, hundreds of thousands to ~1,000,000 rows), the
  first start after upgrading builds these indexes and may take noticeably longer than a
  normal restart. This is expected and one-time; see `docs/releasing.md` for details.

[Unreleased]: https://github.com/hydradns/hydradns/compare/v0.1.1...main
[0.1.1]: https://github.com/hydradns/hydradns/releases/tag/v0.1.1