Skip to content

Release v2.1.x: Penguin-DAL migration, centralized libs, and test suite overhaul - #46

Open
PenguinzTech wants to merge 230 commits into
mainfrom
v2.1.x
Open

Release v2.1.x: Penguin-DAL migration, centralized libs, and test suite overhaul#46
PenguinzTech wants to merge 230 commits into
mainfrom
v2.1.x

Conversation

@PenguinzTech

Copy link
Copy Markdown
Contributor

This pull request merges the v2.1.x branch into main, representing a significant modernization and consolidation of the Squawk codebase.

Key Changes

1. Architectural Consolidation

  • Centralized Libraries: Removed vendored libraries in favor of centralized .
  • Penguin-DAL Migration: Migrated and from PyDAL to , including new SQLAlchemy schemas and Alembic migrations.
  • Service Consolidation: Removed separate and components, integrating DHCP and Time (NTP/PTP) services directly across components.

2. Frontend Modernization

  • React WebUI: Converted the Flask web console to an API-only backend with a modern React-based WebUI ().
  • Dependency Hardening: Pinned and dependencies to exact versions and immutable SHA references.
  • Component Integration: Integrated (LoginPageBuilder, SidebarMenu, AppConsoleVersion) into the frontend applications.

3. Testing & Quality Assurance

  • Comprehensive Test Suite: Added a new directory with E2E (Playwright), Integration, Load, and Smoke tests.
  • Coverage Improvements: Achieved 100% coverage for key frontend components (Login, Sidebar) and improved Python test coverage for backend services.
  • CI/CD Hardening: Updated GitHub Actions workflows with security scanning (Trivy), dependency monitoring, and standardized build processes.

4. Infrastructure & Deployment

  • Standardized K8s: Added standardized Kubernetes deployment configurations and deployment scripts (, ).
  • Security Fixes: Resolved high-severity vulnerabilities in Next.js and other dependencies.

5. Documentation & Standards

  • Unified Documentation: Massive overhaul of and project-wide documentation to meet new base standards.
  • Standardized Templates: Added GitHub issue templates for bugs, features, and security reports.

PenguinzTech and others added 30 commits December 11, 2025 10:18
Version files added (all at v1.0.0):
- dns-client-go/.version - Go DNS client
- dns-server/.version - DNS server
- dns-client/.version - Python DNS client (docker-client)
- dns-server/flask_app/.version - Web console (webui)

Workflow updates:
- Go client workflow now uses dns-client-go/.version
- Build triggers only when component's .version changes
- Added Windows ARM64 to build targets (6 platforms total)
- Release tags now use format: v{VERSION}-go-client

Release notes reorganized with sections for each package:
- go-client, dns-server, docker-client, webui
- Each with independent versioning and changelogs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add .version file monitoring to all build workflows
- Implement epoch64 timestamp-based naming (alpha/beta-<epoch64>)
- Add version-based release naming (vX.X.X-alpha/beta)
- Add auto pre-release creation on .version changes
- Add security scanning (gosec for Go, bandit for Python, npm audit for Node.js)
- Create comprehensive docs/WORKFLOWS.md
- Update docs/STANDARDS.md with CI/CD section
- Update CLAUDE.md with CI/CD section and pre-commit checklist

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Backend:
- Add DHCP models (pools, leases, reservations, servers) with PyDAL
- Add Time models (servers, sync logs, clients, config) with PTP/NTP support
- Add full REST API for DHCP pool management and lease tracking
- Add full REST API for time server management and sync operations
- Fix RBAC middleware: add check_team_access function
- Fix team model: remove unsupported PyDAL unique constraint

Frontend:
- Add TypeScript types for DHCP and Time services
- Add DHCPPools management component with utilization visualization
- Add TimeServers management component with PTP/NTP protocol selection
- Integrate new tabs into Management page

Go Client:
- Add NTP client package with automatic server failover
- Add NTP forwarder to intercept OS time requests (port 123)
- Add time configuration with environment variable support
- Add CLI commands: time query, time forward, time status
- Add comprehensive unit and integration tests

Documentation:
- Update API.md with DHCP and Time API endpoints
- Update ARCHITECTURE.md with network services diagrams
- Update STANDARDS.md with service-specific guidelines

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Restructure from server-side templates to a three-container architecture:
- Flask backend serves JSON-only REST API with JWT + Flask-Login auth
- React WebUI (Vite + TypeScript + Tailwind) as separate nginx container
- DNS server unchanged

Flask changes: add Flask-JWT-Extended, Flask-CORS, Flask-Limiter; convert
all dashboard routes to /api/v1/ JSON endpoints; add safe PyDAL row
serialization (exact type checks to exclude RecordUpdater/LazySet);
enforce auth on all API endpoints.

Docker: add Dockerfile.api for Flask, dns-webui service with nginx
reverse proxy, update docker-compose.yml with new services.

Tests: add comprehensive smoke test suite (130 tests) covering container
health, API endpoints, JWT auth flows, pagination, and response formats.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes Dependabot alerts #33 and #34:
- CVE: Next.js HTTP request deserialization DoS (high)
- CVE: Next.js Image Optimizer DoS (medium)

Also fixes js-yaml prototype pollution via npm audit fix.

Remaining 2 moderate alerts (eslint stack overflow, Next.js PPR memory)
require breaking major version upgrades and have no stable patches.

quic-go alerts (#28-30) are already resolved on this branch (v0.59.0
includes all patches).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The root Dockerfile's dns-server and dns-client stages both COPY docs/
into the image. The .dockerignore was excluding the docs directory,
which would cause the CI docker-multi-build job to fail.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit addresses all identified security vulnerabilities across Python,
Node.js, and Go components, and adds extensive smoke test coverage for both
alpha (local) and beta (K8s) environments to ensure production readiness.

Security Fixes:
- Python: Update cryptography (41→44), PyMySQL (1.1.0→1.1.1), Pillow (10→11),
  aiohttp (3.9→3.11.11), add defusedxml for XXE prevention
- Node.js: Update react-router-dom (6→7), eslint (8→9), vite (5→7),
  next (15→16) to fix XSS, DoS, and stack overflow vulnerabilities
- Go: Fix integer overflow in NTP time handling, replace math/rand with
  crypto/rand, enforce TLS 1.2+ minimum, update quic-go to v0.59.0

New Features:
- Add version management script (scripts/version/update-version.sh)
  supporting major/minor/patch/build version increments
- Add 150 mock tests for isolated component testing (DNS client/server,
  Manager API, database operations)
- Add 79 comprehensive smoke tests covering security, edge cases,
  integration, and user experience for alpha and beta environments

Test Coverage:
- Security: 32 tests (auth, authorization, input validation, injection
  prevention, CORS, TLS, data isolation)
- Edge Cases: 37 tests (boundaries, special chars, concurrency, large
  datasets, error recovery)
- Integration: 39 tests (component communication, data flow, K8s
  integration, monitoring)
- User Experience: 24 tests (page loads, forms, navigation, error messages)
- Mock Tests: 150 tests (fast, isolated, no network/database dependencies)

Version: Bump to v2.1.1.1770072428 (patch increment for security fixes)

Resolves: Critical PyMySQL SQL injection, XSS in react-router, integer
overflow in Go NTP client, weak RNG in HTTP backoff, TLS misconfiguration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Install shared/react_libs dependencies before webui build to resolve
zod module import error during vite build.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update dal2-beta cluster deployment to use the pre-existing
penguintech-wildcard-tls certificate from cert-manager instead of
generating a new certificate.

Changes:
- Create comprehensive K8s manifests in k8s/manifests/
- Configure ingress for squawk.penguintech.io with proper TLS
- Use pre-existing Cloudflare wildcard certificate (penguintech-wildcard-tls)
- Deploy DNS server, Flask API, WebUI, and Valkey services
- Add health checks, resource limits, and HA configuration (2 replicas)
- Configure proper routing: / → webui, /api/v1 → flask-api, /dns-query → dns-server

Ingress Configuration:
- Host: squawk.penguintech.io
- TLS Secret: penguintech-wildcard-tls (Cloudflare wildcard cert)
- SSL redirect enabled
- NGINX ingress class

Deployed to: dal2-beta cluster, squawk namespace

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove restrictive security contexts and add emptyDir volumes for
directories that need write access (database logs, nginx cache).

Fixes:
- flask-api: Health check path /api/v1/health → /health
- flask-api: Add volume mount for /app/flask_app/databases
- webui: Add volume mounts for nginx cache and run directories
- All: Remove runAsUser/runAsNonRoot to allow containers to start properly

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Change @penguin/react_libs reference from local file path to GitHub
repository to ensure consistent versioning across all PenguinTech projects.

- Update package.json to reference github:penguintechinc/penguin-libs#main
- Install @penguintechinc/react-libs from main branch
- Remove dependency on local shared/react_libs copy

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace local copies of shared libraries with git-referenced dependencies
from github.com/penguintechinc/penguin-libs. This ensures consistent
versioning across all PenguinTech projects.

- Remove entire shared/ directory (react_libs, node_libs, go_libs, py_libs,
  licensing, database) including duplicate shared/shared/ nested copy
- Update Dockerfile to remove COPY shared/react_libs build steps
- Update tailwind.config.js content path to node_modules dist
- Update all imports from @penguin/react_libs to @penguintechinc/react-libs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PenguinzTech and others added 22 commits August 31, 2026 09:19
fix(helm): correct DoH path + cache URL (client→server & caching were broken)
fix(go-client): DoH path 404, gRPC cleartext token, bounded reads, mTLS downgrade
…dening

fix(dns-server): stop JWT leak in /metrics, gate metrics+status, harden DoS
…imiting)

- CRITICAL (gRPC): RefreshToken minted a server JWT for any server_id with no
  caller auth; listener used add_insecure_port with no interceptor. Added a
  default-deny ServerAuthInterceptor (requires a valid server JWT in metadata),
  RefreshToken now requires proof (current/near-expiry server JWT matching
  server_id, or a valid join_key), and serve_grpc uses add_secure_port with TLS
  (GRPC_TLS_CERT/KEY/CA_FILE); insecure bind only under SQUAWK_GRPC_INSECURE=true.
- CRITICAL (SAML): assertion signature was never verified (loads()+is_ok()).
  Now uses Saml2Client.parse_authn_request_response so signature/audience/time/
  recipient are enforced; requires SubjectConfirmationData + matching InResponseTo;
  allow_unsolicited=False. Added xmlsec1 to the Dockerfile (pysaml2's DSig backend).
- CRITICAL (rate limiting): limiter trusted client X-Forwarded-For and defaulted
  skip_private_ips=True, so a spoofed header bypassed all limits. Added ProxyFix
  (trusted hop count), skip_private_ips=False, and a key func that only uses the
  proxy-resolved remote_addr. Added per-endpoint limits on login/token/refresh/
  change-password/mfa-verify/SCIM, exponential-backoff account lockout, and a
  single-use MFA pre-auth token (jti consumed on first attempt).

New: app/extensions.py (limiter singleton), alembic 014 (failed_login_count/
locked_until), join_key field on RefreshTokenRequest.

Tests: SAML forged/unsolicited rejection, gRPC interceptor auth (13), rate-limit
XFF-spoof ignored (7), login lockout + MFA single-use (9). flake8 clean;
391 passed (1 pre-existing env-only failure: opentelemetry.instrumentation.flask
missing locally, fails identically on base).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…vulns

fix(manager): close critical auth/transport vulns (gRPC, SAML, rate limiting)
NTP server:
- HIGH: UDP responder never verified the NTS Authenticator EF, enabling
  spoofed-source reflection and defeating NTS integrity. Now verifies the
  authenticator (c2s_key) before responding and drops packets that fail/omit it.
  Added per-source-IP rate limiting (NTP_RATE_LIMIT_PER_SEC, default 20/s).
- MED: NTS-KE issued cookies to any TLS client; now requires a valid
  ntp:client-scoped JWT before issuing cookies.
- MED: NTS-KE TLS floor raised to TLS 1.3 (RFC 8915 §3).
- LOW: cookie now carries the real key-generation version so key rotation no
  longer breaks all cookies (also fixed a latent RFC 7822 EF-padding bug that
  corrupted cookie AES-SIV ciphertext on unseal).
- Feature flag now fails SAFE (deny) when PostHog is unconfigured, matching the
  other services (was fail-open).

DHCP server:
- MED: /dhcp/request stored a client-supplied requested_ip with no range or
  active-lease-conflict check (lease hijack). Added validate_requested_ip and
  applied it before create_lease.
- Input validation: canonical MAC + RFC1123 hostname validation (control-char/
  length checks) on all request handlers before store/log.

Tests: 51 new (NTS authenticator/rate-limit/rotation, DHCP IP/MAC/hostname
validation); 134 passed. flake8 clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(ntp,dhcp): verify NTS authenticator, gate NTS-KE, TLS1.3, validate DHCP input
…wt_secret

The highest-confidence finding of the security audit (flagged by 5 independent
review agents): DNS resolver tokens were stored, compared, and returned in
plaintext.

- DNS tokens: added token_hash (sha256, unique-indexed); tokens hashed on
  create/regenerate, plaintext returned once only and stripped from list/get;
  validate_dns_token (manager) and dns-server selective_dns_routing now hash the
  incoming token and look up by hash. Fixes the team-less-token IDOR (H5) via a
  default-deny _can_manage_token guard.
- dns_server.join_key + deployment_domain.jwt_token: hashed at rest (equality
  lookups → hash comparison).
- dns_server.jwt_secret: Fernet-encrypted at rest (recoverable — used to sign
  server JWTs), decrypted at the 4 direct-read call sites (auth_service,
  grpc_server, middleware/auth, dns_servers) fail-closed.
- Added shared crypto helpers sha256_hex()/get_fernet_cipher().
- Migration 015 backfills all hashes + re-encrypts jwt_secret row-by-row, then
  drops the 3 plaintext columns (batch_alter_table for SQLite). Up+down verified
  end-to-end against a seeded pre-migration fixture.

Also replaced 3 in-scope calls to the nonexistent penguin-dal Row.update_record()
with db(...).update() (see PR note on the broader pre-existing occurrence).

Tests: 17 manager + 3 dns-server (hash create/list/validate/regenerate, IDOR,
join_key hash, jwt_secret encrypt/decrypt round-trip incl. signed-JWT verify).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, users, DHCP/Time, SCIM, DPoP)

- HIGH: public DNS zones were writable by any authenticated user — check_zone_access
  returned True for public zones and gated writes. Added check_zone_write_access
  (read stays public-permissive; writes require zones:write/admin + team ownership);
  create/update_zone reject unowned team_id. New zones:write/admin + config:*/read
  scopes added to role bundles.
- HIGH: client-config routes had no authorization (only @token_required). Added
  config:write/config:admin scope gates on mutations (incl. domain-JWT rollover);
  replaced spoofable X-User-ID header attribution with the authenticated identity.
- HIGH: users:write could assign global_role=SystemAdmin. _can_assign_role now
  requires users:admin to grant any elevated/greater-than-own role.
- HIGH: BOLA on DHCP/Time mutations — added the _can_access_pool/_can_access_server
  guard (404-before-403) to every mutation and re-applied team filtering to the
  list endpoints. Also fixed dead 'globalRole'/user['id'] key lookups (real keys
  are global_role/user_id) that made those guards KeyError for real callers.
- HIGH: SCIM user IDOR — SCIM ops now require external_id (SCIM-provisioned users
  only) and hard-block SystemAdmin targets (full tenant scoping needs a schema
  column that doesn't exist yet — noted).
- MED: DPoP replay race — check-then-insert replaced with insert-first
  (unique-violation = replay).
- LOW: SAML-admin mass assignment allowlisted; client-config str(e) leaks replaced
  with generic messages.

Tests: 6 new access-control regression suites + 1 DPoP unit test (32 pass locally;
dpop proof-validation + observability failures are pre-existing/env-only, fail
identically on the untouched base).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…olation)

The new test relied on dns-server/tests/conftest.py importing the manager's
schema.py to populate the shared token table; in CI's job layout that import
path isn't reachable, conftest silently falls back to empty metadata, and the
test hit TableNotFoundError: 'token'. The test now creates the minimal
post-migration token table (token_hash, no plaintext token) directly against
db_engine with checkfirst=True and only cleans rows on teardown. Production
code untouched. Verified passing twice in an isolated copy with no manager/
sibling (the exact CI condition).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(manager): close broken-access-control vulns (zones, client-config, users, DHCP/Time, SCIM, DPoP)
After merging v2.1.x (access-control PR #91) into this branch, the access-control
test fixture still seeded deployment_domain.jwt_token, which this PR renamed to
jwt_token_hash. Updated the fixture to insert the hashed column. Full manager
suite: 441 passed (1 pre-existing env-only observability failure).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(manager,dns-server): hash DNS tokens/join-keys at rest, encrypt jwt_secret
Pre-existing functional break, confirmed across penguin-dal 0.1.0-0.4.1 and by
reading penguin_dal/query.py: Row has no update_record() and TableProxy has no
__delitem__, so every PUT route (`row.update_record(**f)`) and every DELETE route
(`del db.<table>[id]`) 500'd at runtime. It shipped because these routes had zero
test coverage.

Replaced all 28 call sites across 12 files with the supported QuerySet API:
- `row.update_record(**f)` -> `db(db.<table>.id == row.id).update(**f)` (18 sites)
- `del db.<table>[id]`     -> `db(db.<table>.id == id).delete()`        (9 sites)
- config_service heartbeat + whois_cache update (2 service-internal sites)

Files: ioc_feeds, time, dhcp, auth, sso_admin, teams, zones, users, dns_servers,
tokens blueprints + config_service, whois_service.

Tests: new test_dal_update_delete_regression.py (12 tests) exercises each PUT/
DELETE route end-to-end and re-fetches to assert the change persisted (these fail
against the old code, pass now). Also corrected 4 access-control tests that had
encoded the bug via pytest.raises(AttributeError/TypeError) to assert real
success. Manager suite: 453 passed (1 pre-existing env-only observability fail).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(manager): repair every PUT/DELETE route (penguin-dal API misuse)
…t-time, key perms)

MED/LOW hardening from the security audit:
- init_db no longer prints the admin password to stdout (persisted in container/CI
  logs); prints a masked note instead.
- MFA recovery codes: entropy raised from 32 bits (token_hex(4)) to 64 bits
  (token_hex(8)); bcrypt storage/verify unchanged.
- ProductionConfig now enforces a minimum length (>=32) on the secret that keys
  the MFA/SSO Fernet encryption, failing startup if too weak (dev/test untouched).
- whois + ioc_feeds error paths no longer return str(e) to clients — detail logged
  server-side, generic message returned (via responses.internal_error).
- Browser-binding SHA-256 comparisons in sso_service/saml_service switched from
  != to hmac.compare_digest.
- dns-server cert_manager writes key files atomically at mode 0600
  (os.open O_CREAT|O_WRONLY) instead of write-then-chmod, closing the readable
  race window.
- Removed a pre-existing unused import (datetime.timedelta) in sso_service.py.

Tests: 6 new suites (password hygiene, recovery entropy, key-strength, const-time
binding, error-response hygiene, cert key perms). manager 468 passed, dns-server
155 passed (1 pre-existing env-only observability failure).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(security): crypto & secret hygiene (password print, entropy, const-time, key perms)
A security product must record security-relevant events. The audit subsystem
existed (@audit_log on ~60 routes) but the auth lifecycle was uninstrumented.
Extended coverage using the existing decorator to:
- token refresh, machine/NHI token grants (client_credentials + token-exchange,
  incl. failed client auth), MFA verify (success+failure), SSO/OIDC login,
  SAML login/ACS, SCIM user CRUD + token mint/revoke, deployment-domain JWT
  rollover.
- Fixed the actor-attribution gap: user_login rows were written with
  actor_id=NULL (get_current_user() is None during login), so brute force was
  untraceable to the targeted account. login now records the TARGET account id +
  source IP for success and failure.

Mechanism: audit_log now honors optional g.audit_actor_id/tenant/resource_type/
resource_id set by the view for pre-auth events, falling back to the existing
JWT/kwarg inference; added a defensive per-invocation reset of those g attrs.

Tests: test_audit_logging_coverage.py (8 tests) — refresh, failed/successful/
unknown-username login attribution, MFA-verify failure, SSO login, SCIM create,
domain-JWT rollover. flake8 clean; 461 passed (1 pre-existing env-only failure).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(manager): close audit-logging gaps on auth-lifecycle events
Audit found the CI security gates masked (can't fail) and no real CodeQL despite
the org ruleset requiring code-scanning.

- Add .github/workflows/codeql.yml: real CodeQL analysis (python, go,
  javascript-typescript), SHA-pinned action, on push/PR to main+v2.1.x+release/**
  plus weekly cron. Provides the code-scanning results the org ruleset requires
  and adds the previously-absent JS/TS SAST coverage.
- Unmask bandit (build.yml, version-monitor.yml): JSON-report run kept, followed
  by a real gating run on the same inputs (no || true, continue-on-error: false),
  scoped to app source.
- Unmask gosec: replaced the -no-fail masked action with a gating
  `gosec ./...` on squawk-client-go; also fixed 4 ineffective //nolint:gosec
  suppressions to gosec-native `// #nosec` syntax (they were silently unsuppressed).
- Resolved the real findings the now-gating scanners surfaced:
  * ntp-server binds: `# nosec B104` justified (network daemon must listen broadly)
  * dhcp.py/schema.py listen_address DB defaults: `# nosec B104` (config data, not
    a socket bind)
  * squawk-client k8s-client.py: added missing requests timeout (B113, real bug)
- Coverage gate (--cov-fail-under=98 || true) left non-gating with a rationale
  comment — actual coverage is below threshold; raising it is a separate effort.

Verified: bandit gating command exits 0, gosec exits 0, go build passes, all
workflow YAML valid, actions SHA-pinned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…deql

chore(ci): add CodeQL analysis + unmask bandit/gosec security gates
Comment thread dhcp-server/app/db.py
db((db.dhcp_lease.mac_address == mac) & (db.dhcp_lease.pool_id == pool_id)).update(
ip_address=ip, hostname=hostname, lease_start=now, lease_end=lease_end, status="active"
)
logger.info(f"Renewed lease: {mac} -> {ip}")
Comment thread dhcp-server/app/db.py
lease_end=lease_end,
status="active",
)
logger.info(f"Created lease: {mac} -> {ip}")
Comment thread dhcp-server/app/db.py
existing = db((db.dhcp_lease.mac_address == mac) & (db.dhcp_lease.pool_id == pool_id)).select().first()

if not existing:
logger.warning(f"Release: lease not found for {mac}")
Comment thread dhcp-server/app/db.py

db((db.dhcp_lease.mac_address == mac) & (db.dhcp_lease.pool_id == pool_id)).update(status="released")

logger.info(f"Released lease: {mac}")
Comment thread dns-server/tests/test_ioc_checker.py Fixed
Comment thread dns-server/tests/test_ioc_checker.py Fixed
Comment thread dns-server/tests/test_ioc_checker.py Fixed
Comment thread dns-server/tests/test_ioc_checker.py Fixed
Comment thread dns-server/tests/test_ioc_checker.py Fixed
Comment thread dns-server/tests_full_future/test_cert_manager.py Fixed
PenguinzTech and others added 7 commits September 2, 2026 14:21
Release is gated on 90% coverage; the dns-server coverage check was masked
(--cov-fail-under=98 || true) and hid that real coverage was ~45%. It was also
missing a test-only dependency (`responses`), so two suites failed collection
and the gate would have errored even if unmasked.

- Added 14 unit-test suites covering the previously-thin modules to ~99-100%:
  cert_manager, selective_dns_routing, selective_router, manager_client,
  resilience, config, main, http3_serving, dns_resolver, cache_manager,
  prometheus_metrics, metrics_reporter, observability, grpc_server. Real
  behavior + edge/error paths, mocked externals.
- Fixed a real DoS bug found while testing: prometheus_metrics.py imported
  `prometheus_client.Counter` over `collections.Counter` (name shadow), so the
  top_domains cap trim always raised (silently swallowed) and the dict was never
  bounded. Aliased to CollectionsCounter; the cap now actually enforces.
- CI (build.yml, server-release.yml): install requirements-dev.txt in the
  coverage step (provides `responses`), remove the `|| true` mask, and set
  --cov-fail-under=90 so the gate is real.

Local lower bound (excluding the 2 responses-dependent suites): 99% (1951 stmts,
25 missing). flake8 clean on all files; workflows valid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… hardening)

dns-webui kept access + refresh JWTs in localStorage, so any JavaScript (app or a
compromised dependency) could exfiltrate them. Moved to HttpOnly, Secure,
SameSite=Strict cookies that JS cannot read. Change is ADDITIVE — bearer-token
clients (manager/frontend, Go client) are unaffected.

Backend (manager):
- app/services/cookie_auth.py (new): set/clear HttpOnly+Secure+SameSite=Strict
  access/refresh cookies + a JS-readable csrf_token cookie; double-submit CSRF
  check (secrets.compare_digest).
- middleware/auth.py: token_required accepts the cookie as a fallback to the
  Authorization header; enforces CSRF on cookie-sourced mutating requests only
  (header clients exempt).
- blueprints/auth.py (login/refresh/logout), mfa.py (mfa_verify): set cookies
  alongside the existing JSON tokens; logout clears cookies + revokes the
  cookie-sourced refresh token.
- config.py: COOKIE_SECURE/COOKIE_DOMAIN (dev/test default to non-Secure).

Frontend (dns-webui):
- Removed ALL localStorage token read/write; api uses withCredentials:true and
  auto-attaches the CSRF header on mutating requests; 401 -> cookie-based refresh.
- useAuth tracks auth state via /auth/me, never holding the JWT in JS.

Tests: 16 backend cookie/CSRF tests + updated frontend Login test. Backend suite
492 passed (1 pre-existing env-only observability failure); dns-webui vitest 9
passed. flake8 clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two CI failures on the coverage gate:
- test_manager_client_coverage.py uses the `requests_mock` fixture from the
  requests-mock package, which was installed locally but not declared — CI
  errored with "fixture 'requests_mock' not found". Added requests-mock to
  dns-server/requirements-dev.txt (installed by the coverage step).
- CodeQL raised 2 high false positives (py/incomplete-url-substring-sanitization)
  on membership checks in the new test files (`"host" in dict`, `"name" in list`
  — not URL sanitization). Added .github/codeql/codeql-config.yml with
  paths-ignore for test/vendored code and wired it into codeql.yml init;
  production code is still scanned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…kies

fix(dns-webui): store JWTs in HttpOnly cookies, not localStorage (XSS hardening)
CI (stricter env) surfaced 3 test failures on the coverage run:
- cert_manager.create_server_cert used the host FQDN as the X.509 CommonName
  with no length cap; a >64-char hostname (e.g. the CI runner's) raised
  ValueError during cert generation -- a real bug for long-hostname hosts. CN is
  now truncated to 64 chars; the full hostname still goes in the SAN.
- prometheus_metrics system-metrics tests require psutil (an optional guarded
  import in the module); added psutil to requirements-dev.txt so the psutil path
  runs under test.

Coverage gate itself already passed (97.86% >= 90%). All 134 cert/prometheus
tests pass locally; flake8 clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 40 selective-routing coverage tests passed in the pip test env but failed in
CI's Docker-image test run (`docker run ... pytest tests/`) with
TableNotFoundError for dns_group / dns_routing_zone / user_group_assignment /
group_zone_access. Like the token-hash test before it, they relied on conftest
importing the manager schema to create those tables, which silently no-ops when
manager/ isn't checked out alongside (the Docker case).

Added an autouse fixture that creates the four tables directly against db_engine
(checkfirst=True, row-only teardown), mirroring test_selective_dns_routing_
token_hash.py. Also fixed one test that used a token but hadn't declared the
token_table fixture. Verified against a fresh copy with no manager/ sibling
(60 passed, twice); coverage still 100%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test(dns-server): raise coverage to ~99% and enforce the 90% gate
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.

2 participants