Thanks to visit codestin.com
Credit goes to github.com

Skip to content

hash passwords and API keys in main config#3257

Merged
deacon-mp merged 10 commits into
masterfrom
hash-creds
Mar 16, 2026
Merged

hash passwords and API keys in main config#3257
deacon-mp merged 10 commits into
masterfrom
hash-creds

Conversation

@uruwhy
Copy link
Copy Markdown
Contributor

@uruwhy uruwhy commented Feb 25, 2026

Description

  • Addresses the security issue where user passwords and primary API keys were stored in plaintext in the main caldera. config
  • Overwrites the configuration file on startup with hashed user passwords and red/blue API keys
  • Any newly created configuration files will have hashed passwords and API keys
  • password and API key checks are performed by comparing hashes instead of plaintext comparisons
  • hashing is done via Argon2 per OWASP recommendations
  • LDAP login handler is not affected since Caldera does not manage LDAP credentials or directly perform LDAP authentication
  • update to fieldmanual documentation will require a separate PR for that plugin repo

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • This change requires a documentation update

How Has This Been Tested?

  • unit tests
  • tested user login for red and blue groups
  • tested API key usage
  • interacted with UI
  • tested updating password in config file
  • tested adding new users in config file
  • tested creating a new local.yml config file
  • tested creating a new config file on startup using server.py's -E argument

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
64.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
70.3% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

deacon-mp
deacon-mp previously approved these changes Mar 15, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR secures Caldera’s main configuration by hashing stored user passwords and red/blue API keys with Argon2, and updating authentication flows to verify hashes instead of plaintext comparisons.

Changes:

  • Introduces app/utility/config_util.py to generate secure local configs and to hash/verify config credentials (Argon2).
  • Extends BaseWorld.apply_config() with apply_hash / overwrite_path to hash sensitive fields and optionally rewrite the source config file.
  • Updates auth/login logic and tests to operate on hashed credentials; adds argon2-cffi dependency.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/web_server/test_core_endpoints.py Loads main config with hashing enabled in web server tests
tests/utility/test_config_util.py Adds unit tests for hashing/verification and local config generation
tests/services/test_rest_svc.py Updates test config setup to use apply_hash=True
tests/conftest.py Ensures session-wide test config is hashed when applied
tests/api/v2/test_security.py Updates security tests to apply hashed config
tests/api/v2/test_responses.py Updates response middleware tests to apply hashed config
tests/api/v2/test_knowledge.py Updates knowledge API tests to apply hashed config
tests/api/v2/managers/test_config_api_manager.py Updates config manager tests to apply hashed main config
server.py Switches to new config utility + hashes/overwrites config on startup
requirements.txt Adds argon2-cffi dependency
app/utility/config_util.py New hashing/verification + secure config generation utilities
app/utility/config_generator.py Removes previous config generator implementation
app/utility/base_world.py Adds config hashing + optional overwrite support in apply_config
app/service/login_handlers/default.py Uses hash verification for password checks
app/service/auth_svc.py Uses hash verification for API key authentication

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +41 to +44
ph = PasswordHasher()
try:
return ph.verify(hash_val, target)
except (VerifyMismatchError, VerificationError, InvalidHashError):
Comment thread app/utility/config_util.py Outdated
Comment thread app/utility/base_world.py Outdated
Comment on lines +38 to +41
if changes_made and overwrite_path:
logging.debug(f'Overwriting config file {overwrite_path} with secure values')
with open(overwrite_path, 'w') as cfg_file:
cfg_file.write(yaml.dump(config))
Comment thread app/utility/base_world.py Outdated
Comment on lines +40 to +41
with open(overwrite_path, 'w') as cfg_file:
cfg_file.write(yaml.dump(config))
Comment thread tests/utility/test_config_util.py Outdated
Comment on lines +35 to +38
hash = '$argon2id$v=19$m=65536,t=3,p=4$87lgOXDGx/9JUHuCsxlaZw$bcJp3dQcqMiYdZOCm8LLJ8ncaEwjoS1xVcPHUGs/ajU'
plaintext = 'testpassword'
assert verify_hash(hash, plaintext)
assert not verify_hash(hash, 'testpassword2')
Comment thread server.py
Comment on lines +242 to +243
BaseWorld.apply_config("main", BaseWorld.strip_yml(main_config_path)[0], apply_hash=True,
overwrite_path=main_config_path)
Comment thread app/service/auth_svc.py
Comment on lines +173 to 176
elif verify_hash(self.get_config(CONFIG_API_KEY_RED), request.headers.get(HEADER_API_KEY)):
return self.Access.RED, self.Access.APP
elif request.headers.get(HEADER_API_KEY) == self.get_config(CONFIG_API_KEY_BLUE):
elif verify_hash(self.get_config(CONFIG_API_KEY_BLUE), request.headers.get(HEADER_API_KEY)):
return self.Access.BLUE, self.Access.APP
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens Caldera’s main configuration handling by Argon2-hashing stored user passwords and primary API keys, optionally overwriting the on-disk config with the hashed values, and updating authentication to verify via hashes.

Changes:

  • Introduces app.utility.config_util with Argon2 hashing/verification and secure local config generation.
  • Updates config loading (BaseWorld.apply_config, server.py) to hash credentials on startup and optionally rewrite config files.
  • Updates auth/login code paths and tests to expect and validate hashed passwords/API keys.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
app/utility/config_util.py New hashing/verification utilities + secure local config generation.
app/utility/config_generator.py Removed legacy local config generator (replaced by config_util).
app/utility/base_world.py Adds apply_hash + optional config overwrite-on-hash behavior.
app/service/auth_svc.py Switches API key checks from plaintext compare to Argon2 verification.
app/service/login_handlers/default.py Switches password checks from plaintext compare to Argon2 verification.
server.py Uses ensure_local_config from new module; hashes main config on load and overwrites when needed.
requirements.txt Adds argon2-cffi dependency.
tests/utility/test_config_util.py New unit tests for hashing/verification and local config generation.
tests/conftest.py Loads main config with apply_hash=True in fixtures.
tests/web_server/test_core_endpoints.py Loads main config with apply_hash=True for endpoint tests.
tests/services/test_rest_svc.py Applies main config with hashing enabled in rest service tests.
tests/api/v2/test_security.py Applies config with hashing enabled in API v2 security tests.
tests/api/v2/test_responses.py Applies config with hashing enabled in API v2 response tests.
tests/api/v2/test_knowledge.py Applies config with hashing enabled in API v2 knowledge tests.
tests/api/v2/managers/test_config_api_manager.py Applies main config with hashing enabled in config API manager tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/service/auth_svc.py
Comment on lines 168 to 176
async def get_permissions(self, request):
identity_policy = request.config_dict.get('aiohttp_security_identity_policy')
identity = await identity_policy.identify(request)
if identity in self.user_map:
return [self.Access[p.upper()] for p in self.user_map[identity].permissions]
elif request.headers.get(HEADER_API_KEY) == self.get_config(CONFIG_API_KEY_RED):
elif verify_hash(self.get_config(CONFIG_API_KEY_RED), request.headers.get(HEADER_API_KEY)):
return self.Access.RED, self.Access.APP
elif request.headers.get(HEADER_API_KEY) == self.get_config(CONFIG_API_KEY_BLUE):
elif verify_hash(self.get_config(CONFIG_API_KEY_BLUE), request.headers.get(HEADER_API_KEY)):
return self.Access.BLUE, self.Access.APP
Comment on lines +41 to +44
ph = PasswordHasher()
try:
return ph.verify(hash_val, target)
except (VerifyMismatchError, VerificationError, InvalidHashError):
Comment on lines +64 to +69
# Hash credentials
for group_name, group_dict in config.get('users', dict()).items():
for username, val in group_dict.items():
if not _is_hashed(val):
config['users'][group_name][username] = ph.hash(val)
any_hashed = True
Comment thread app/utility/base_world.py Outdated
Comment on lines +36 to +41
if apply_hash:
changes_made = hash_config_creds(config)
if changes_made and overwrite_path:
logging.debug(f'Overwriting config file {overwrite_path} with secure values')
with open(overwrite_path, 'w') as cfg_file:
cfg_file.write(yaml.dump(config))
Comment thread app/service/auth_svc.py
Comment on lines 141 to 149
def request_has_valid_api_key(self, request):
request_api_key = request.headers.get(HEADER_API_KEY)
if request_api_key is None:
return False
for i in [CONFIG_API_KEY_RED, CONFIG_API_KEY_BLUE]:
api_key = self.get_config(i)
if api_key is not None and compare_digest(request_api_key, api_key):
hashed_api_key = self.get_config(i)
if hashed_api_key is not None and verify_hash(hashed_api_key, request_api_key):
return True
return False
Comment thread tests/utility/test_config_util.py Outdated
Comment on lines +35 to +38
hash = '$argon2id$v=19$m=65536,t=3,p=4$87lgOXDGx/9JUHuCsxlaZw$bcJp3dQcqMiYdZOCm8LLJ8ncaEwjoS1xVcPHUGs/ajU'
plaintext = 'testpassword'
assert verify_hash(hash, plaintext)
assert not verify_hash(hash, 'testpassword2')
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR strengthens Caldera’s configuration security by hashing user passwords and primary API keys (red/blue) using Argon2, updating startup/config generation flows accordingly, and adjusting authentication to verify hashes rather than compare plaintext.

Changes:

  • Introduces config_util with Argon2 hashing + verification helpers and updates local config generation.
  • Extends BaseWorld.apply_config to optionally hash credentials and overwrite config files with hashed values on startup.
  • Updates auth/login logic and tests to work with hashed credentials and API keys.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/web_server/test_core_endpoints.py Loads main config with hashing enabled for endpoint tests.
tests/utility/test_config_util.py Adds unit tests for hashing/verification and local config generation.
tests/services/test_rest_svc.py Updates test config loading to pass apply_hash=True.
tests/conftest.py Ensures test fixtures load hashed main config.
tests/api/v2/test_security.py Updates security tests to load hashed users/API key.
tests/api/v2/test_responses.py Updates responses tests to load hashed users.
tests/api/v2/test_knowledge.py Updates knowledge tests to load hashed main config.
tests/api/v2/managers/test_config_api_manager.py Updates config manager tests to hash main config on load.
server.py Uses new config_util.ensure_local_config and overwrites main config with hashed values at startup.
requirements.txt Adds argon2-cffi dependency pin.
app/utility/config_util.py New module implementing Argon2 hashing/verification and secure local.yml generation.
app/utility/config_generator.py Removes old config generator implementation.
app/utility/base_world.py Adds apply_hash + overwrite_path behavior for config hashing and rewrite.
app/service/login_handlers/default.py Verifies passwords via Argon2 hash instead of plaintext compare.
app/service/auth_svc.py Verifies API keys via Argon2 hash instead of constant-time plaintext compare.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/utility/base_world.py Outdated
Comment on lines +38 to +41
if changes_made and overwrite_path:
logging.debug(f'Overwriting config file {overwrite_path} with secure values')
with open(overwrite_path, 'w') as cfg_file:
cfg_file.write(yaml.dump(config))
blue=dict(blue=secrets.token_urlsafe()))

# Display API keys and user credentials, then hash them
logging.info(CONFIG_MSG_TEMPLATE.render(config_path=LOCAL_CONF_PATH, **config))
Comment thread app/service/auth_svc.py
Comment on lines +173 to 176
elif verify_hash(self.get_config(CONFIG_API_KEY_RED), request.headers.get(HEADER_API_KEY)):
return self.Access.RED, self.Access.APP
elif request.headers.get(HEADER_API_KEY) == self.get_config(CONFIG_API_KEY_BLUE):
elif verify_hash(self.get_config(CONFIG_API_KEY_BLUE), request.headers.get(HEADER_API_KEY)):
return self.Access.BLUE, self.Access.APP
Comment thread app/service/auth_svc.py
Comment on lines 141 to 149
def request_has_valid_api_key(self, request):
request_api_key = request.headers.get(HEADER_API_KEY)
if request_api_key is None:
return False
for i in [CONFIG_API_KEY_RED, CONFIG_API_KEY_BLUE]:
api_key = self.get_config(i)
if api_key is not None and compare_digest(request_api_key, api_key):
hashed_api_key = self.get_config(i)
if hashed_api_key is not None and verify_hash(hashed_api_key, request_api_key):
return True
return False
ph = PasswordHasher()
try:
return ph.verify(hash_val, target)
except (VerifyMismatchError, VerificationError, InvalidHashError):
Comment thread app/utility/config_util.py Outdated


def _is_hashed(val):
return val.startswith('$argon2id$')
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens Caldera’s configuration handling by Argon2-hashing user passwords and red/blue API keys, rewriting configs on startup with hashed values, and updating authentication checks to verify hashes instead of comparing plaintext.

Changes:

  • Add app.utility.config_util with helpers to hash credentials, verify Argon2 hashes, and generate a secure conf/local.yml.
  • Update server startup and tests to apply config hashing (apply_hash=True) and overwrite configs with hashed secrets.
  • Update auth and default login flows to validate API keys/passwords via Argon2 verification; add Argon2 dependency and unit tests.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/web_server/test_core_endpoints.py Loads main config with hashing enabled in web server tests.
tests/utility/test_config_util.py Adds unit tests for hashing/verification and local config generation.
tests/services/test_rest_svc.py Updates test config loading to hash secrets.
tests/conftest.py Ensures session/app fixtures hash main config on load.
tests/api/v2/test_security.py Updates BaseWorld config setup to hash secrets.
tests/api/v2/test_responses.py Updates BaseWorld config setup to hash secrets.
tests/api/v2/test_knowledge.py Updates BaseWorld config setup to hash secrets.
tests/api/v2/managers/test_config_api_manager.py Ensures main config is hashed for manager tests.
server.py Switches secure-config helper import and overwrites main config with hashed values on startup.
requirements.txt Adds argon2-cffi dependency pin.
app/utility/config_util.py New hashing/verification + secure local.yml generation logic.
app/utility/config_generator.py Removes old local config generator in favor of config_util.
app/utility/base_world.py Extends apply_config to hash secrets and optionally overwrite config files.
app/service/login_handlers/default.py Switches password validation to Argon2 verification.
app/service/auth_svc.py Switches API key validation to Argon2 verification.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +37 to +45
def verify_hash(hash_val, target):
"""
Returns True if the argon2 hash for the target matches hash_val, False otherwise.
"""
ph = PasswordHasher()
try:
return ph.verify(hash_val, target)
except (VerifyMismatchError, VerificationError, InvalidHashError):
return False
Comment thread tests/utility/test_config_util.py Outdated
assert verify_hash(config['users']['group2']['user2'], 'testpassword2')

@mock.patch.object(PasswordHasher, 'hash', return_value='mockhash')
@mock.patch.object(yaml, 'safe_load', return_value=SENSITIVE_CONF)
Comment thread tests/utility/test_config_util.py Outdated
Comment on lines +35 to +38
hash = '$argon2id$v=19$m=65536,t=3,p=4$87lgOXDGx/9JUHuCsxlaZw$bcJp3dQcqMiYdZOCm8LLJ8ncaEwjoS1xVcPHUGs/ajU'
plaintext = 'testpassword'
assert verify_hash(hash, plaintext)
assert not verify_hash(hash, 'testpassword2')
Comment thread app/service/auth_svc.py
Comment on lines +173 to 176
elif verify_hash(self.get_config(CONFIG_API_KEY_RED), request.headers.get(HEADER_API_KEY)):
return self.Access.RED, self.Access.APP
elif request.headers.get(HEADER_API_KEY) == self.get_config(CONFIG_API_KEY_BLUE):
elif verify_hash(self.get_config(CONFIG_API_KEY_BLUE), request.headers.get(HEADER_API_KEY)):
return self.Access.BLUE, self.Access.APP
Comment thread app/utility/base_world.py Outdated
if changes_made and overwrite_path:
logging.debug(f'Overwriting config file {overwrite_path} with secure values')
with open(overwrite_path, 'w') as cfg_file:
cfg_file.write(yaml.dump(config))
Comment thread app/utility/base_world.py Outdated
Comment on lines +39 to +41
logging.debug(f'Overwriting config file {overwrite_path} with secure values')
with open(overwrite_path, 'w') as cfg_file:
cfg_file.write(yaml.dump(config))

logging.info('Creating new secure config in %s' % local_conf_path)
with local_conf_path.open('w') as fle:
yaml.safe_dump(make_secure_config(), fle, default_flow_style=False)
Comment on lines +84 to +86
# Display API keys and user credentials, then hash them
logging.info(CONFIG_MSG_TEMPLATE.render(config_path=LOCAL_CONF_PATH, **config))
hash_config_creds(config)
@deacon-mp deacon-mp requested a review from Copilot March 16, 2026 01:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses plaintext storage of user passwords and primary API keys in Caldera config by hashing credentials (Argon2) and updating authentication checks to verify hashes instead of doing plaintext comparisons.

Changes:

  • Introduces app.utility.config_util for hashing/verifying credentials and securely generating conf/local.yml.
  • Updates auth/login logic to verify API keys and passwords against stored Argon2 hashes.
  • Updates config loading paths/tests to hash credentials on load and optionally overwrite config files with hashed values.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/web_server/test_core_endpoints.py Loads main config with hashing enabled for web server endpoint tests.
tests/utility/test_config_util.py Adds unit tests for hashing/verification and secure config generation behavior.
tests/services/test_rest_svc.py Applies main config with hashing enabled in REST service tests.
tests/conftest.py Applies hashed main config for shared test fixtures.
tests/api/v2/test_security.py Applies hashed main config in v2 security tests.
tests/api/v2/test_responses.py Applies hashed main config in v2 response tests.
tests/api/v2/test_knowledge.py Applies hashed main config in v2 knowledge tests.
tests/api/v2/managers/test_config_api_manager.py Applies hashed main config in config manager tests.
server.py Switches to config_util.ensure_local_config and overwrites main config with hashed secrets on startup.
requirements.txt Adds Argon2 dependency.
app/utility/config_util.py New utility for hashing/verifying secrets and generating secure local config.
app/utility/config_generator.py Removes legacy config generator (replaced by config_util).
app/utility/base_world.py Adds options to hash credentials and overwrite the on-disk config when changes are made.
app/service/login_handlers/default.py Updates password checks to use hash verification.
app/service/auth_svc.py Updates API key checks and permissions resolution to use hash verification.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +12 to +27
CONFIG_MSG_TEMPLATE = jinja2.Template("""
Log into Caldera with the following admin credentials:
Red:
{%- if users.red.red %}
USERNAME: red
PASSWORD: {{ users.red.red }}
{%- endif %}
API_TOKEN: {{ api_key_red }}
Blue:
{%- if users.blue.blue %}
USERNAME: blue
PASSWORD: {{ users.blue.blue }}
{%- endif %}
API_TOKEN: {{ api_key_blue }}
To modify these values, edit the {{ config_path }} file and restart Caldera.
""")
blue=dict(blue=secrets.token_urlsafe()))

# Display API keys and user credentials, then hash them
logging.info(CONFIG_MSG_TEMPLATE.render(config_path=LOCAL_CONF_PATH, **config))
Comment on lines +44 to +46
ph = PasswordHasher()
try:
return ph.verify(hash_val, target)
Modifies the configuration dictionary parameter.
Returns True if any values were modified (hashed), False otherwise.
"""
ph = PasswordHasher()
Comment on lines +63 to +70
if val and not _is_hashed(val):
config[option] = ph.hash(val)
any_hashed = True

# Hash credentials
for group_name, group_dict in config.get('users', dict()).items():
for username, val in group_dict.items():
if not _is_hashed(val):
Comment thread app/utility/base_world.py

@staticmethod
def apply_config(name, config):
def apply_config(name, config, apply_hash=False, overwrite_path=''):
Comment thread app/utility/base_world.py
Comment on lines +36 to +41
if apply_hash:
changes_made = hash_config_creds(config)
if changes_made and overwrite_path:
logging.debug(f'Overwriting config file {overwrite_path} with secure values')
with open(overwrite_path, 'w') as cfg_file:
yaml.safe_dump(config, cfg_file, default_flow_style=False)
uruwhy and others added 10 commits March 15, 2026 21:29
…nfig overwrite

- verify_hash() now returns False for non-string inputs instead of raising TypeError
  (prevents 500 errors when API key header is absent and None is passed to verify)
- base_world.py overwrite now uses yaml.safe_dump for safe, consistent output
- test: rename 'hash' variable to 'hash_val' to avoid shadowing built-in
- test: add None-input assertions to test_verify_hash
- test: use side_effect=deepcopy to prevent SENSITIVE_CONF module-level mutation
@deacon-mp deacon-mp requested a review from Copilot March 16, 2026 01:29
@deacon-mp deacon-mp merged commit b6156b9 into master Mar 16, 2026
15 checks passed
@deacon-mp deacon-mp deleted the hash-creds branch March 16, 2026 01:31
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses plaintext credential storage by introducing Argon2 hashing for configured user passwords and red/blue API keys, and persisting the hashed values back into the selected main config on startup.

Changes:

  • Add app.utility.config_util with Argon2 hashing/verification helpers and secure local config generation.
  • Update config loading (BaseWorld.apply_config and server.py) to hash credentials and optionally overwrite the source YAML with hashed values.
  • Switch authentication checks (password + API key) to hash verification and update tests accordingly.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
app/utility/config_util.py New hashing + verification utilities and secure local config generation/logging.
app/utility/base_world.py Extends apply_config to hash credentials and optionally overwrite the config file.
app/service/auth_svc.py Replaces plaintext API key comparison with hash verification.
app/service/login_handlers/default.py Replaces plaintext password comparison with hash verification.
server.py Uses new ensure_local_config and enables hashing + overwrite for main config on startup.
app/utility/config_generator.py Removed legacy secure-config generator module.
requirements.txt Adds argon2-cffi dependency.
tests/utility/test_config_util.py Adds unit tests for hashing/verification and local config creation behavior.
tests/conftest.py Loads test configs with apply_hash=True to match new auth behavior.
tests/web_server/test_core_endpoints.py Loads test config with hashing enabled.
tests/services/test_rest_svc.py Updates test config loading call signature.
tests/api/v2/test_security.py Loads test config with hashing enabled.
tests/api/v2/test_responses.py Loads test config with hashing enabled.
tests/api/v2/test_knowledge.py Loads test config with hashing enabled.
tests/api/v2/managers/test_config_api_manager.py Loads test config with hashing enabled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +90 to +112
@mock.patch('logging.info')
@mock.patch.object(yaml, 'safe_load', return_value={'app.contact.http': '0.0.0.0', 'plugins': ['sandcat']})
@mock.patch.object(secrets, 'token_urlsafe', return_value='plaintextsecret')
def test_make_secure_config_logs_plaintext_then_hashes(self, mock_token_urlsafe, mock_safe_load, mock_logging_info):
with mock.patch.object(builtins, 'open', mock.mock_open()):
config = make_secure_config()

# logging.info must have been called exactly once (to display startup credentials)
mock_logging_info.assert_called_once()
logged_message = mock_logging_info.call_args[0][0]

# The logged message must contain the plaintext secret so the admin can read their credentials
assert 'plaintextsecret' in logged_message, (
"Expected plaintext secret in logged startup message, got: %r" % logged_message
)

# The returned config must store argon2 hashes, not the plaintext secret
assert config['api_key_blue'].startswith('$argon2id$'), (
"api_key_blue should be an argon2 hash after make_secure_config, got: %r" % config['api_key_blue']
)
assert config['api_key_red'].startswith('$argon2id$'), (
"api_key_red should be an argon2 hash after make_secure_config, got: %r" % config['api_key_red']
)
Comment thread app/utility/base_world.py
Comment on lines +36 to +41
if apply_hash:
changes_made = hash_config_creds(config)
if changes_made and overwrite_path:
logging.debug(f'Overwriting config file {overwrite_path} with secure values')
with open(overwrite_path, 'w') as cfg_file:
yaml.safe_dump(config, cfg_file, default_flow_style=False)
Comment on lines +56 to +73
"""
ph = PasswordHasher()
any_hashed = False
for option in HASHED_OPTIONS:
val = config.get(option, '')

# Skip any values that are already hashed
if val and not _is_hashed(val):
config[option] = ph.hash(val)
any_hashed = True

# Hash credentials
for group_name, group_dict in config.get('users', dict()).items():
for username, val in group_dict.items():
if not _is_hashed(val):
config['users'][group_name][username] = ph.hash(val)
any_hashed = True

Comment on lines +87 to +89
# Display API keys and user credentials, then hash them
logging.info(CONFIG_MSG_TEMPLATE.render(config_path=LOCAL_CONF_PATH, **config))
hash_config_creds(config)
Comment thread app/service/auth_svc.py
Comment on lines 141 to 148
def request_has_valid_api_key(self, request):
request_api_key = request.headers.get(HEADER_API_KEY)
if request_api_key is None:
return False
for i in [CONFIG_API_KEY_RED, CONFIG_API_KEY_BLUE]:
api_key = self.get_config(i)
if api_key is not None and compare_digest(request_api_key, api_key):
hashed_api_key = self.get_config(i)
if hashed_api_key is not None and verify_hash(hashed_api_key, request_api_key):
return True
fionamccrae pushed a commit that referenced this pull request Mar 16, 2026
* hash passwords and API keys in main config

* style fixes

* remove superfluous line

* move hash checks to utility function

* simplify code

* add unit tests for config util

* fix: guard _is_hashed against non-string config values

* test: verify make_secure_config logs plaintext once then returns hashes

* style: remove unused logging import from test_config_util.py (F401)

* fix: guard verify_hash against None inputs; use yaml.safe_dump for config overwrite

- verify_hash() now returns False for non-string inputs instead of raising TypeError
  (prevents 500 errors when API key header is absent and None is passed to verify)
- base_world.py overwrite now uses yaml.safe_dump for safe, consistent output
- test: rename 'hash' variable to 'hash_val' to avoid shadowing built-in
- test: add None-input assertions to test_verify_hash
- test: use side_effect=deepcopy to prevent SENSITIVE_CONF module-level mutation

---------

Co-authored-by: deacon <[email protected]>
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.

3 participants