Skip to content

Add PEPPER to password handling for improved security#81

Open
JonanOribe wants to merge 10 commits intosmittix:mainfrom
JonanOribe:PEPPER
Open

Add PEPPER to password handling for improved security#81
JonanOribe wants to merge 10 commits intosmittix:mainfrom
JonanOribe:PEPPER

Conversation

@JonanOribe
Copy link
Contributor

Introduces a PEPPER value from config and appends it to passwords during both admin user creation and login authentication. This enhances password security by making hashes more resilient to attacks.

Introduces a PEPPER value from config and appends it to passwords during both admin user creation and login authentication. This enhances password security by making hashes more resilient to attacks.
Copy link
Owner

@smittix smittix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! The pepper concept is good for defense-in-depth, but there are two issues that need to be addressed before merging:

1. Hardcoded Default Pepper

PEPPER = _get_env('PEPPER', '7aJBLRSimPO0l6Wkfic8YhO3PPqwPwD7oikTfK4TYjzIFxS4Tk')

A pepper's security comes from being secret and not in the source code. With a hardcoded default, anyone with access to the repo has the pepper, defeating its purpose.

Suggested fix: Either:

  • Require explicit configuration (no default) and fail startup if not set
  • Generate a random pepper on first run and store it in the database or a local file outside the repo
  • Document that users must set a custom PEPPER env var in production

2. Breaking Change for Existing Accounts

After this change, existing admin accounts will fail to authenticate because:

  • Existing password hash = hash(password)
  • New login check = hash(password + pepper)

These won't match.

Suggested fix: Add a migration path:

  • Option A: On first run after upgrade, detect missing pepper and rehash existing passwords
  • Option B: Add a one-time migration command/script
  • Option C: At minimum, document that users need to reset their admin password after upgrading

Once these are addressed, happy to merge!

@JonanOribe
Copy link
Contributor Author

JonanOribe commented Jan 25, 2026

Thank you for the detailed feedback. These are excellent points regarding security and the upgrade path. I will be implementing the following changes to address them:

  1. Removal of Hardcoded Pepper & Secure Storage Strategy

I will remove the hardcoded default pepper to ensure the secret is never part of the source code. I will adopt the following approach:

  • Fail-fast Initialization: I’ll modify the startup logic to require an explicit PEPPER environment variable. The application will throw a ConfigurationError and refuse to start if it is missing.

  • Storage Outside the Database: I will document that the pepper must be stored in the OS environment or a local config file outside the web root. I am avoiding the database storage option because if the DB is ever compromised, the pepper would be stolen alongside the hashes, defeating the defense-in-depth purpose. This approach ensures a proper Separation of Secrets and works consistently across Linux and macOS. Furthermore, in the future, we could use this PEPPER as a Master Secret for an HKDF (Key Derivation Function). This would allow us to derive unique, cryptographically independent keys for different purposes, such as one for password hashing and another for encrypting sensitive SDR recordings or private messages. This way, even if one sub-key were somehow leaked, the others remain secure. By keeping the root secret outside the database, we ensure that even in the event of a full data breach, the most sensitive content remains encrypted and unreadable without the server-side secret

  1. Migration Path for Existing Accounts

To handle the transition without locking out current users, I will implement a "Lazy Rehash" strategy:

  • Dual Verification Logic: The authentication process will first attempt to verify the password using hash(password + pepper).

  • On-the-fly Migration: If that fails, it will fallback to the legacy hash(password). If the legacy check succeeds, the system will automatically re-hash the password with the new pepper and update the database record immediately.

  • Documentation: I will also add a note to the changelog/migration guide so users are aware of this security hardening.

To be honest, I left the PEPPER hardcoded, much like the default database username and password, assuming that the first thing any user would do after cloning the repo for deployment is customize these values. However, I agree that your approach is much cleaner and more secure.

I will push these updates as soon as they are ready.

Introduces secure PEPPER-based password hashing for user authentication. Updates documentation and setup to require a unique INTERCEPT_PEPPER environment variable, modifies login logic to use the new verification method, and adds automatic migration for legacy password hashes. Updates dependencies and Docker configuration to support these changes.
@JonanOribe
Copy link
Contributor Author

I've implemented the requested changes. This includes both the UV requirements and functionality, as well as Docker and setup.sh. I've tested it, but please review it carefully in case you find anything missing, as there are many possible combinations and I don't want to have overlooked anything.

I've also added the missing dependencies to the .toml file after the latest updates.

Copy link
Owner

@smittix smittix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! The pepper implementation and lazy migration approach are solid concepts. However, there are a few issues that need to be addressed before this can be merged:

Critical: PEPPER can be None - No fail-fast validation

The author mentioned implementing "fail-fast initialization" in the comments, but it wasn't actually implemented:

# config.py:150
PEPPER = os.environ.get('INTERCEPT_PEPPER')  # Returns None if not set

When INTERCEPT_PEPPER is not set:

  • f"{password}{PEPPER}" becomes "adminNone" instead of failing
  • Admin user gets created with hash of "adminNone"
  • If PEPPER is later set, all existing users get locked out

Suggested fix: Add startup validation in app.py (in main()) or config.py:

if PEPPER is None:
    raise RuntimeError("INTERCEPT_PEPPER environment variable must be set. See README for setup instructions.")

Debug print() statements left in production code

# utils/database.py:36, 52, 57
print(f"Verifying user: {username} at {datetime.now()}")
print(f"User '{username}' new verified at {datetime.now()}")
print(f"User '{username}' legacy verified at {datetime.now()}")

These should be logger.debug() calls or removed entirely.

Duplicate import

# utils/database.py:15
from werkzeug.security import generate_password_hash, check_password_hash
# utils/database.py:27
from werkzeug.security import check_password_hash, generate_password_hash

The second import on line 27 is redundant and should be removed.

Missing test coverage

The new verify_user() function has no test coverage. Consider adding tests for:

  • Successful login with peppered hash
  • Legacy hash detection and migration
  • Failed login attempts
  • Behavior when PEPPER is properly set

Once these are addressed, this will be good to merge!

Require the INTERCEPT_PEPPER environment variable at startup and raise an error if unset. Add comprehensive tests for user verification, including legacy password hash migration to peppered hashes, successful and failed logins, and user-not-found scenarios. Also, remove unused PEPPER import from app.py.
Deleting prints
@JonanOribe
Copy link
Contributor Author

Now, both when launching the project and when running the tests, if PEPPER does not exist, it simply won't let you run it and will warn you via the console. For the other use cases, I added the tests, as you requested.

image

@JonanOribe JonanOribe requested a review from smittix January 29, 2026 15:05
Copy link
Owner

@smittix smittix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding PEPPER support - this is a solid security enhancement! The migration logic for legacy passwords is well thought out.

However, there are some issues that should be addressed before merging:

1. Hard crash on missing PEPPER is too aggressive

PEPPER = os.environ.get('INTERCEPT_PEPPER')
if PEPPER is None:
    raise RuntimeError("INTERCEPT_PEPPER environment variable must be set...")

This will immediately break all existing installations on upgrade. Consider:

  • Log a warning but allow the app to start in "legacy mode" for a grace period
  • Or generate a random PEPPER on first run and save it to a file/DB
  • At minimum, make this a loud warning rather than a hard crash

2. Docker placeholder is dangerous

- INTERCEPT_PEPPER=your_generated_secret_here

If users don't change this, all Docker installs share the same PEPPER, defeating the security benefit entirely. Instead:

  • Remove from docker-compose.yml
  • Add to .env.example with clear instructions
  • Document that users MUST set this before first run

3. Unused import left in app.py

The refactor moves password checking to verify_user(), but check_password_hash import from werkzeug will be left unused in app.py. Please remove it.

4. Unrelated dependency additions

These in pyproject.toml don't seem related to PEPPER:

'psycopg2-binary>=2.9.9',
'numpy>=1.24.0', 
'scipy>=1.10.0'

Please remove or split into a separate PR.

5. Whitespace noise in config.py

The diff shows trailing whitespace changes throughout config.py that obscure the actual changes. Please revert the whitespace-only changes.


The core security feature is good - just needs these cleanup items addressed. Let me know if you have questions!

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