> For the complete documentation index, see [llms.txt](https://docs.openalgo.in/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openalgo.in/change-log/release/version-2.0.0.9-released.md).

# Version 2.0.0.9 Released

**Date: 1st May 2026**

**Patch: Manual-Rotation Guidance Hardened — Never Recommend Rotating `API_KEY_PEPPER` on a Populated Database**

This is a **single-commit patch release** on top of v2.0.0.8's Docker upgrade hotfix. The auto-rotation in `utils/env_check.py` already gates `API_KEY_PEPPER` rotation on database state — it only rotates the pepper on a fresh install with no users, because rotating it on a populated DB would invalidate every Argon2 password hash and every Fernet-encrypted broker auth/feed token / TradingView API key, none of which can be recovered. But the **fallback error-path messaging** — printed when the rotation cannot write to `.env` (e.g. read-only mount, permission denied) — was still telling users to manually regenerate *both* values. A user with a populated DB who followed that recipe would brick their deployment. Same problem in the `start.sh` pre-flight banner added in v2.0.0.8. v2.0.0.9 makes the user-facing manual-rotation guidance match the auto-rotation gating.

***

**Highlights**

* **Error path now DB-aware** — `utils/env_check.py` branches the manual-rotation message on `db_populated`. Populated DB → instructs only `APP_KEY` rotation, with an explicit "DO NOT change `API_KEY_PEPPER`" warning and a pointer to `upgrade/rotate_pepper.py`. Fresh DB → both can be safely regenerated.
* **`start.sh` pre-flight banner rewritten** — Default advice is `APP_KEY`-only. A second prominent block warns against regenerating `API_KEY_PEPPER` with the reasoning, and points to `upgrade/rotate_pepper.py` for the controlled path.

***

**Security**

* `b9301b78` — `fix(security): never recommend rotating API_KEY_PEPPER on populated DB`

The auto-rotation logic in `utils/env_check.py:329-412` is correct: on a populated DB, only `APP_KEY` is rotated; `API_KEY_PEPPER` is deliberately left alone because rotating the pepper invalidates:

* Every Argon2 password hash in `database/user_db.py` (one-way, cannot be migrated).
* Every Fernet-encrypted broker auth/feed token in `database/auth_db.py`.
* Every Fernet-encrypted TradingView API key.

But the **fallback error-path** — taken when the rotation can't write `.env.tmp` (read-only mount, EACCES) — printed:

```
Detected publicly-known APP_KEY/API_KEY_PEPPER in .env, but
could not rewrite the file (...).

Generate fresh values manually and paste them into .env:
  python -c "import secrets; print(secrets.token_hex(32))"
```

That advice is safe on a fresh install, fatal on a populated one. Same issue in the `start.sh` pre-flight banner added in v2.0.0.8 — it instructed users to `sed`-replace both `APP_KEY` and `API_KEY_PEPPER`.

Fixes shipped:

* **`utils/env_check.py`** — error message branches on `db_populated`. Populated DB prints `APP_KEY`-only instructions plus an explicit, bold "DO NOT change `API_KEY_PEPPER` on this populated install" warning, with the reasoning and a pointer to `upgrade/rotate_pepper.py` for the controlled rotation path that handles re-encryption + password reset. Fresh DB prints the original "both can be regenerated" guidance.
* **`start.sh`** — pre-flight banner rewritten. Default action is `APP_KEY`-only:

  ```bash
  APP_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
  sed -i "s|^APP_KEY *=.*|APP_KEY = '$APP_KEY'|" .env
  sudo chown 1000:1000 .env
  sudo chmod 600 .env
  ```

  A second prominent banner block — `[OpenAlgo] DO NOT regenerate API_KEY_PEPPER` — explains why and points to the rotate\_pepper.py migration. PEPPER rotation is only safe on installs with no users; on any other install, leave it alone and let the auto-rotation's silent fast path take over once `APP_KEY` is no longer compromised.

The `_generate_keys_on_first_run` decision matrix (already documented in `utils/env_check.py:332-345`) is unchanged. v2.0.0.9 only tightens the *user-facing manual-rotation guidance* to mirror the same gating.

**Safe upgrade procedure for users on a populated install hitting the v2.0.0.6 → v2.0.0.8 Docker crash:**

```bash
docker compose down
APP_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
sed -i "s|^APP_KEY *=.*|APP_KEY = '$APP_KEY'|" .env
# Do NOT touch API_KEY_PEPPER on a populated DB.
sudo chown 1000:1000 .env
sudo chmod 600 .env
docker compose up -d
```

After this, `_generate_keys_on_first_run` takes the silent fast path for `APP_KEY` (no longer in `COMPROMISED_APP_KEYS`). The pepper remains in the compromised set, but `db_populated=True` gates the rotation off — only a single warning line, no startup block. Browser sessions need to log in again — by-design, that's how `APP_KEY` rotation prevents anyone with the leaked sample key from forging your sessions.

***

**Contributors**

* **@marketcalls (Rajandran)** — security hardening of manual-rotation guidance; PEPPER-safety review against the populated-DB upgrade path.

***

**Links**

* **Repository**: <https://github.com/marketcalls/openalgo>
* **Documentation**: <https://docs.openalgo.in>
* **Discord**: <https://www.openalgo.in/discord>
* **YouTube**: <https://www.youtube.com/@openalgo>
* **Issue tracker**: <https://github.com/marketcalls/openalgo/issues>

***
