Security: Fix timing-unsafe bridge admin check and secure agent API mutations#5014
Security: Fix timing-unsafe bridge admin check and secure agent API mutations#5014Stevenn28 wants to merge 3 commits into
Conversation
|
Welcome to RustChain! Thanks for your first pull request. Before we review, please make sure:
Bounty tiers: Micro (1-10 RTC) | Standard (20-50) | Major (75-100) | Critical (100-150) A maintainer will review your PR soon. Thanks for contributing! |
Code Review: Fix timing-unsafe bridge admin check and secure relationship mutationsSummaryCombines two security fixes:
Comparison with our PRs
Notes
Both PRs fix the same bugs. #5014 uses a cleaner decorator pattern but reveals more info via 500 errors. Review quality: Comparative review |
galpetame
left a comment
There was a problem hiding this comment.
@galpetame requesting changes after local validation.
The bridge compare_digest() change itself is directionally right, and the existing relationship tests still pass, but this PR introduces/keeps a few merge blockers that should be fixed before landing:
- The new admin guards in
contributor_registry.py,payout_ledger.py, andbounties/issue-2285/src/memory_routes.pyacceptadmin_keyfrom the URL query string:
req_key = request.headers.get("X-Admin-Key") or request.args.get("admin_key")
if req_key != ADMIN_KEY:That puts an admin secret into URLs, browser history, proxy/server logs, referrers, and monitoring tools. It also keeps direct string equality instead of hmac.compare_digest(). I verified this on contributor_registry.py: /approve/probeuser?admin_key=secret-query-probe returned 302, while the same route without a key returned 401.
- The no-config path leaks server configuration as HTTP 500. Probes:
POST /bridge/release with no BRIDGE_ADMIN_KEY -> 500 {"error": "admin key not configured on server"}
POST /api/relationships/a/b/disagree with no RELATIONSHIPS_ADMIN_KEY -> 500 {"error": "relationships admin key not configured on server"}
For auth guards, this should fail closed with a generic 401/403 rather than exposing exact server config state to unauthenticated callers.
git diff --check origin/main...HEAD -- agent_relationships.py bridge\bridge_api.py contributor_registry.py payout_ledger.py bounties\issue-2285\src\memory_routes.py node\machine_passport_api.pyfails:
agent_relationships.py:1039: trailing whitespace.
contributor_registry.py:162: trailing whitespace.
node/machine_passport_api.py:212: trailing whitespace.
node/machine_passport_api.py:313: trailing whitespace.
Validation I ran:
pytest test_agent_relationships.py -q -> 40 passed
pytest bridge\test_bridge_api.py -q -> 31 passed after creating C:\tmp for the repo's Windows /tmp test paths
python -m py_compile agent_relationships.py bridge\bridge_api.py contributor_registry.py payout_ledger.py bounties\issue-2285\src\memory_routes.py node\machine_passport_api.py -> passed
python tools\bcos_spdx_check.py --base-ref origin/main -> BCOS SPDX check: OK
git diff --check ... -> failed on the whitespace above
Suggested fix: keep admin keys header-only, compare with hmac.compare_digest(), return a generic fail-closed 401/403 when an admin key is not configured, and remove the whitespace errors.
Bounty #73 payout wallet if this review is eligible: RTCe4fbe4c9085b8b2ed3f1228504de66799025f6ce
loganoe
left a comment
There was a problem hiding this comment.
I found merge blockers in this PR. The bridge compare-digest change is directionally good, but the broader patch leaves several affected checks red.
Findings:
agent_relationships.py:1036re-importsosinsidecreate_relationship_blueprinteven though the module already imports it at line 29; targeted ruff reports F811.git diff --check origin/main...HEADfails on trailing whitespace inagent_relationships.py,contributor_registry.py, andnode/machine_passport_api.py.- The memory route tests are not updated for the new clear auth behavior:
bounties/issue-2285/tests/test_memory_routes.py::MemoryRoutesTestCase::test_clear_memorynow gets 403 instead of 200. - Contributor registry and machine passport tests are still written for the old auth/validation contract: duplicate registration posts an invalid new wallet and no longer reaches duplicate handling; unauthenticated approve now gets 403; machine passport create-without-admin now gets 401, causing update auth tests to hit 404 setup failures.
- The admin guards added in
contributor_registry.py,payout_ledger.py, andbounties/issue-2285/src/memory_routes.pystill acceptadmin_keyfrom the query string and use direct string equality. For a security hardening PR, please keep admin secrets header-only and usehmac.compare_digest.
Validation run:
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 uv run --no-project --with pytest --with flask python -m pytest test_agent_relationships.py -q-> 40 passedPYTEST_DISABLE_PLUGIN_AUTOLOAD=1 uv run --no-project --with pytest --with flask --with flask-cors --with pyyaml --with pycryptodome --with prometheus-client python -m pytest bridge/test_bridge_api.py -q-> 31 passedPYTHONPATH=bounties/issue-2285/src ... pytest bounties/issue-2285/tests/test_memory_routes.py -q-> 1 failed, 29 passed, 11 subtests passed... pytest tests/test_contributor_registry.py tests/test_payout_ledger_migration.py tests/test_machine_passport_event_json_validation.py node/tests/test_machine_passport.py -q-> 5 failed, 40 passedpython3 -m py_compile agent_relationships.py bridge/bridge_api.py contributor_registry.py payout_ledger.py bounties/issue-2285/src/memory_routes.py node/machine_passport_api.py-> passeduv run --no-project --with ruff ruff check ... --select E9,F821,F811,F841 --output-format=concise-> F811 inagent_relationships.py
Please clean up the lint/diff issues, remove query-string admin credentials, and update the affected auth tests before merge.
jaxint
left a comment
There was a problem hiding this comment.
LGTM! Thanks for contributing. Approved.
TJCurnutte
left a comment
There was a problem hiding this comment.
I found a blocker in the new admin gates.
Validation I ran:
git diff --check origin/main...HEAD -- agent_relationships.py bounties/issue-2285/src/memory_routes.py bridge/bridge_api.py contributor_registry.py node/machine_passport_api.py payout_ledger.py
# failed: trailing whitespace in agent_relationships.py, contributor_registry.py, and node/machine_passport_api.py
python3 -B -m py_compile agent_relationships.py bounties/issue-2285/src/memory_routes.py bridge/bridge_api.py contributor_registry.py node/machine_passport_api.py payout_ledger.py
# py_compile_exit=0
# focused Flask probes with ADMIN_KEY=sekrit
contributor_registry GET /approve/alice => 401
contributor_registry GET /approve/alice?admin_key=sekrit => 302
contributor_registry status after query-auth approve => approved
payout_ledger POST /api/ledger no header => 401
payout_ledger POST /api/ledger?admin_key=sekrit => 201
payout_ledger POST /api/ledger X-Admin-Key => 201
payout_ledger rows inserted => 2
contributor_registry.py query_param_admin_key=True hmac_compare_digest_present=False
payout_ledger.py query_param_admin_key=True hmac_compare_digest_present=False
bounties/issue-2285/src/memory_routes.py query_param_admin_key=True hmac_compare_digest_present=False
The PR is trying to secure admin mutations, but the new decorators in contributor_registry.py, payout_ledger.py, and bounties/issue-2285/src/memory_routes.py still accept ?admin_key=. That puts the admin secret in URLs, access logs, browser history, and referrers; the probe above shows it is enough to approve a contributor and create payout ledger rows without any X-Admin-Key header.
Those same decorators also use plain != rather than hmac.compare_digest, so the timing-safety fix is only applied to bridge/bridge_api.py and agent_relationships.py, not to the new admin gates added elsewhere.
Requested changes:
- Drop the query-string
admin_keyfallback for mutating/admin routes. - Use
hmac.compare_digestfor all admin-key comparisons, not just the bridge/relationship paths. - Add regression tests that prove
?admin_key=sekritis rejected whileX-Admin-Key: sekritsucceeds. - Clean up the
git diff --checkwhitespace failures.
himanalot
left a comment
There was a problem hiding this comment.
Requesting changes.
The bridge compare_digest change is fine, but this PR also adds several new admin_required decorators that accept the admin secret from request.args.get("admin_key") and compare it with req_key != ADMIN_KEY (contributor_registry.py, payout_ledger.py, and bounties/issue-2285/src/memory_routes.py). That puts admin secrets in URLs, where they are routinely captured in access logs, browser history, analytics, and referrers, and the newly added comparisons are not timing-safe.
Please make these new admin checks header-only and use a constant-time comparison, or remove the unrelated endpoint changes from this PR and keep the patch scoped to the bridge timing fix plus the agent relationship mutations.
|
Paid 10 RTC of 40 RTC cluster (PRs #5011 #5012 #5014 #5080). tx_hash: 0ff9c8d19252e2edca197859e66845ad | pending #1510 | confirms in 24h Closing as merge-conflict. All 4 PRs add overlapping To unblock: rebase a single consolidated PR off latest main with all 4 hardenings in one diff, and we'll merge it as a follow-on (no additional bounty — the work is already paid). — triage 2026-05-14 |
HCIE2054
left a comment
There was a problem hiding this comment.
LGTM! Thanks for contributing!
Description\n\nCloses #5000\nCloses #5009\n\nThis PR fixes two security vulnerabilities:\n\n1. Timing-unsafe bridge API (#5000): Changed the direct string comparison
key != BRIDGE_ADMIN_KEYto the timing-safehmac.compare_digest(key, BRIDGE_ADMIN_KEY)insidebridge/bridge_api.pyto prevent observable timing discrepancies that could lead to key recovery.\n\n2. Unauthenticated Agent relationships mutation (#5009): Added a@require_admindecorator insideagent_relationships.pyto protect the/disagree,/collaborate,/reconcile, and/intervenePOST endpoints. Previously these endpoints were totally unauthenticated, allowing any user to maliciously alter agent relationships and manipulate trust scores.