Follow-up review fixes for #9419:
- _is_prefork_worker() only read argv, so a pool selected through
CELERY_WORKER_POOL (notably `threads`, which runs tasks in the main process
and never dispatches worker_process_init) was misclassified as prefork. The
providers were then deferred to a signal that never fires and the worker
exported nothing. _effective_pool() now applies Celery's own precedence:
-P/--pool flag, then worker_pool from settings, then the prefork default.
- Pass OTEL_EXPORTER_OTLP_TRACES_ENDPOINT / _METRICS_ENDPOINT through
x-otel-env. is_otel_active() and the exporters both honor them, so an
operator setting only those had telemetry silently disabled in every
container.
- Guard the collector's second json_parser with an `if` on attributes.log:
filelog.include matches every container on the host, so plaintext logs from
postgres/redis/rabbitmq logged a parse error per line.
- Cover the pool detection with unit tests, including the settings-configured
and CLI-overrides-settings cases.
Claude-Session: https://claude.ai/code/session_01BpGkdpVLNQ3Ziqcd6zK2qV
Review fixes for #9419:
- Pin headers on the license telemetry exporter so it can no longer inherit
OTEL_EXPORTER_OTLP_HEADERS and ship an operator's APM credential to
telemetry.plane.so. An empty dict is falsy and does not suppress the
exporters' env fallback, so the value has to be non-empty.
- manage.py: apply --settings before the first plane.* import, which otherwise
materializes LazySettings from the default module and silently ignored the
flag (breaking the local runserver entrypoint).
- Defer OTLP exporter creation to worker_process_init on the Celery prefork
pool; the gRPC channel is created eagerly and is not fork-safe.
- Bound interpreter-exit flushing: shutdown_on_exit=False on both providers plus
an atexit hook on the existing bounded flush_otel(). With an unreachable
collector a manage.py command now exits in ~3.5s instead of ~63s.
- Stamp a per-process service.instance.id so gunicorn workers and prefork
children stop exporting colliding cumulative http.server.* streams.
- Resolve instrumentors lazily and isolate each import, and take
opentelemetry-instrumentation-httpx[instruments] so a missing httpx cannot
crash every entrypoint with OTEL_ENABLED=0.
- Add a shared is_otel_active() (enabled AND any endpoint var, signal-specific
included) used by setup, both settings modules and celery, so the log schema
never changes in a process that exports nothing.
- Emit the boot banner through an explicit stderr handler and name the
observability loggers in LOGGING so disable_existing_loggers stops silencing
export errors.
- Strip whitespace in _protocol(); treat blank env values as unset so compose's
${VAR:-} interpolation no longer defeats the sampler defaults.
- Deployments: give migrator the OTel env, spell out the sampler defaults, and
ship OTEL_ENABLED commented out on AIO, where plane.env is exported after
container env and clobbered it.
- Docs: correct the emitted-metrics list, the log-schema gate, and the filelog
pipeline (Docker envelope needs a second json_parser; trace context needs
trace_parser, not move).
- Tests: fix the conftest env leak (monkeypatch.delenv, delete-only teardown)
and cover the new gating, defaults, deferred providers and logging config.
Claude-Session: https://claude.ai/code/session_01BpGkdpVLNQ3Ziqcd6zK2qV
- Extract shared is_otel_enabled() + extend_logging_config() into
plane.observability.logging so the bootstrap, Celery, and Django LOGGING
gates all accept the documented tokens (incl. 'on') identically.
- Isolate each instrumentor in _instrument_libraries() so one failure can't
block startup.
- Pass OTEL_TRACES_SAMPLER / _ARG / OTEL_RESOURCE_ATTRIBUTES through the
x-otel-env compose anchor (documented in .env.example).
- Reset _TRACER_PROVIDER/_METER_PROVIDER in the test fixture.
* fix: remove hardcoded SECRET_KEY from community deployment manifests (GHSA-cmwv-pjmw-8483)
Replace the publicly-known default SECRET_KEY and LIVE_SERVER_SECRET_KEY values
in AIO and CLI community deployment manifests with a safe placeholder.
- deployments/aio: variables.env now ships with placeholder values;
start.sh auto-generates a random key on first boot (or on upgrade from the old
insecure default) and persists it in plane.env across restarts
- deployments/cli: variables.env ships with placeholder; docker-compose.yml
fallbacks that referenced the publicly-known default are removed
- apps/api/plane/settings/common.py: SECRET_KEY resolution now uses `or`
so an empty env var falls back to get_random_secret_key() (not ""); adds a
startup warning if the known insecure default or placeholder is detected
Closes WEB-7805
Co-authored-by: Plane AI <noreply@plane.so>
* fix: use logger.critical instead of print for insecure SECRET_KEY warning
Address code review feedback — replace module-level print() with _logger.critical()
and move _logger definition before the SECRET_KEY block to avoid duplicate assignment.
Also removes the now-unused `import sys`.
Co-authored-by: Plane AI <noreply@plane.so>
---------
Co-authored-by: Plane AI <noreply@plane.so>
* fix(api): rate-limit magic-code verification and bound per-token attempts
The magic-link sign-in / sign-up endpoints accept a 6-digit numeric code
(900k-value space, 600s TTL) but never increment a failure counter on a
wrong-code verify and extend django.views.View rather than DRF APIView,
so DRF's AuthenticationThrottle never runs against them. The space-side
generate endpoint also lacked throttle_classes. Combined, this allowed
an unauthenticated attacker who knew a victim's email to brute-force
the code within the TTL window and log in as the victim.
- Add MAX_VERIFY_ATTEMPTS=5 in MagicCodeProvider.set_user_data: failed
comparisons now persist verify_attempts in Redis under the remaining
TTL and, on hitting the limit, delete the key and raise
EMAIL_CODE_ATTEMPT_EXHAUSTED. This is the load-bearing fix - it caps
total attempts per issued token regardless of request rate.
- Add authentication_throttle_allows() so plain Django Views can apply
AuthenticationThrottle without converting to APIView (would change
CSRF + request-parsing semantics for the redirect-flow endpoints).
- Apply the throttle to MagicSignIn/UpEndpoint and the space variants;
add throttle_classes to MagicGenerateSpaceEndpoint to match its app
sibling.
Refs GHSA-9pvm-fcf6-9234.
* fix(api): make verify-attempt increment atomic, expose throttle rate via env
Address PR review feedback:
- Replace the JSON read-modify-write of verify_attempts with a Lua
EVAL script that INCRs a dedicated counter key and EXPIREs it only
on the first increment. The previous round-trip was racy: parallel
wrong-code requests could read the same value and both write the
same incremented count, letting an attacker exceed MAX_VERIFY_ATTEMPTS
under concurrency. Counter is now reset on each new token issuance
and cleared on successful verify / exhaustion.
- Make AuthenticationThrottle.rate configurable via the
AUTHENTICATION_RATE_LIMIT env var (default 10/minute, down from 30
to tighten the budget on unauth auth-adjacent endpoints). Document
it in deployments/aio and deployments/cli variables.env.
* test(api): cover magic-code attempt cap, counter reset, and auth throttle
Add the contract tests called out in the PR test plan:
- TestMagicSignInVerifyAttempts:
- test_exhausted_after_max_wrong_attempts: after MAX_VERIFY_ATTEMPTS
wrong codes the next verify redirects with EMAIL_CODE_ATTEMPT_
EXHAUSTED_SIGN_IN and both Redis keys are deleted; a follow-up
verify reports EXPIRED.
- test_counter_increments_on_each_wrong_attempt: the dedicated
verify_attempts counter advances by exactly one per wrong POST,
matching the atomic Lua INCR.
- test_counter_resets_on_token_regeneration: regenerating the
magic-link clears the counter so the user isn't pre-locked-out by
a prior session's wrong attempts.
- TestMagicSignUpVerifyAttempts.test_signup_exhausted_after_max_wrong_attempts:
the sign-up endpoint returns EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP on
the exhausting attempt.
- TestAuthenticationThrottle: exercises authentication_throttle_allows
on the plain-View redirect-flow endpoints by patching the rate down
and asserting RATE_LIMIT_EXCEEDED is appended to the redirect URL
once the per-IP budget is exceeded, for both magic-sign-in and
magic-sign-up.
Each new class clears Django cache (DRF throttle storage) and the
per-email Redis keys around every test so runs are independent.
* fix(api): clamp remaining_ttl to >=1 for verify-attempt counter EXPIRE
ri.ttl() returns 0 when the token has less than one second remaining
(Redis floors to whole seconds). The previous clamp only caught
None and < 0, so a sub-second TTL would pass through and the Lua
script's EXPIRE counter 0 would immediately delete the key — letting
an attacker bypass MAX_VERIFY_ATTEMPTS during the final second of the
token's life. Switch the comparison to <= 0.
Narrow real-world impact (sub-second window, throttle still bounds
the rate) but the cap should hold regardless of timing.
* fix: add WEBHOOK_ALLOWED_HOSTS allowlist for internal webhook targets
The IP-based allowlist alone isn't practical for containerised deployments
where service IPs are dynamic. Adds a hostname-based bypass for trusted
internal services (e.g. Silo via docker-compose / k8s service DNS) and
makes the previously hardcoded ["plane.so"] domain blocklist configurable
via WEBHOOK_DISALLOWED_DOMAINS.
- validate_url accepts allowed_hosts (exact, case-insensitive match;
skips DNS lookup for trusted names)
- WebhookSerializer wires both settings through and lets allowlisted
hosts bypass the disallowed-domain check
- Exposes WEBHOOK_ALLOWED_HOSTS in aio/cli deployment env files
* fix: default WEBHOOK_DISALLOWED_DOMAINS to empty for self-hosted
* fix: pass WEBHOOK_ALLOWED_HOSTS to send-time webhook re-validation
* chore: update docker-compose.yml to change restart policy condition from 'on-failure' to 'any' and remove SSL variable from variables.env
* fix: update docker-compose.yml to change restart policy condition from 'any' to 'on-failure'
* fix: update Dockerfile and docker-compose for version v0.28.0 and improve curl commands in install script
* fix: update docker-compose to use 'stable' tag for all services
* fix: improve curl command options in install script for better reliability
* fix: improve API service readiness check in install script
* fix(cli): correct python indentation in api health check
* fix(cli): prevent false positive api ready message on timeout
* Remove deprecated Nginx configuration files and scripts, including Dockerfiles, environment scripts, and configuration templates, to streamline the project structure.
* Update environment configuration and Docker setup for proxy services
- Added LISTEN_PORT and LISTEN_SSL_PORT variables to .env.example and related files.
- Updated Docker Compose files to reference new port variables instead of deprecated NGINX_PORT.
- Adjusted README and variable documentation to reflect changes in port configuration.
- Changed build context for proxy services to use the new directory structure.
* Refactor port configuration in environment and Docker files
- Renamed LISTEN_PORT and LISTEN_SSL_PORT to LISTEN_HTTP_PORT and LISTEN_HTTPS_PORT in .env.example and related files.
- Updated Docker Compose configurations to reflect the new port variable names.
- Adjusted documentation in README and variables.env to ensure consistency with the new naming conventions.
* refactor: reorganize deployment structure and update build workflows
- Restructure deployment directories from deploy/ to deployments/
- Move selfhost files to deployments/cli/community/
- Add new AIO community deployment setup
- Update GitHub Actions workflows for new directory structure
- Add Caddy proxy configuration for CE deployment
- Remove deprecated AIO build files and workflows
- Update build context paths in install scripts
* chore: update Dockerfile and supervisor configuration
- Changed `apk add` command in Dockerfile to use `--no-cache` for better image size management.
- Updated `build.sh` to ensure proper directory navigation with quotes around `dirname "$0"`.
- Modified `supervisor.conf` to set `stderr_logfile_maxbytes` to 50MB and added `stderr_logfile_backups` for better log management across multiple services.
* chore: consistent node and python version
---------
Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>