Skip to content

Reject non-string proof metadata keys#252

Closed
kbaajjg-max wants to merge 1 commit into
ramimbo:mainfrom
kbaajjg-max:codex/reject-proof-metadata-keys
Closed

Reject non-string proof metadata keys#252
kbaajjg-max wants to merge 1 commit into
ramimbo:mainfrom
kbaajjg-max:codex/reject-proof-metadata-keys

Conversation

@kbaajjg-max
Copy link
Copy Markdown

Summary

  • validate proof verifier metadata keys before canonical JSON serialization
  • add a regression test that non-string metadata keys raise LedgerError without changing payout state

Why

pay_bounty() copies verifier_result and later serializes it through canonical JSON with sorted keys. If a service caller passes a metadata dict with non-string keys, serialization can raise a raw TypeError instead of returning a controlled ledger validation error.

Validation

  • .\.venv\Scripts\python.exe -m pytest -q (206 passed, 2 warnings)
  • .\.venv\Scripts\python.exe -m ruff check app\ledger\service.py tests\test_security.py
  • .\.venv\Scripts\python.exe -m ruff format --check app\ledger\service.py tests\test_security.py

Bounty #228

Copy link
Copy Markdown
Contributor

@tolga-tom-nook tolga-tom-nook left a comment

Choose a reason for hiding this comment

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

No blockers found. I reviewed the proof metadata sanitization path in app/ledger/service.py and the new regression in tests/test_security.py.

Evidence:

  • Confirmed _clean_proof_metadata() now rejects non-string metadata keys before proof/ledger entry creation.
  • Confirmed the regression exercises pay_bounty() with a mixed string/int verifier_result and verifies no award count or account balance mutation after LedgerError.
  • Ran ./.venv/bin/python -m pytest tests/test_security.py -q — 33 passed.
  • Ran ./.venv/bin/python -m ruff check app/ledger/service.py tests/test_security.py — passed.
  • Ran ./.venv/bin/python -m mypy app — passed.

Copy link
Copy Markdown

@weilixiong weilixiong left a comment

Choose a reason for hiding this comment

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

Review ✅ APPROVED

QUALITY_GATE: LOW — 2f/14+, reject non-string proof metadata keys.

  • Validates proof metadata keys are strings before storage
  • Clean type checking + error handling
  • Test coverage confirmed by author

Bounty: MW #219 (round 7, 40 MRWK)

Copy link
Copy Markdown
Contributor

@AugmentSecurity AugmentSecurity left a comment

Choose a reason for hiding this comment

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

I found one remaining canonical-JSON validation gap before this is complete.

The PR rejects non-string verifier_result keys, but non-JSON-serializable values still pass _clean_proof_metadata() and fail later in canonical_json(clean_verifier_result) with a raw TypeError. That happens after the bounty award counter update has already run in the current session, so the failure is still not a controlled ledger validation error.

Repro on this PR head:

pay_bounty(..., verifier_result={"label": "mrwk:accepted", "raw": object()})

TypeError: Object of type object is not JSON serializable
awards_paid=1
alice_balance=0

Suggested fix: validate verifier_result for canonical JSON serializability before any payout mutation, or explicitly restrict metadata values to JSON primitives, lists, and dicts with string keys. The regression should assert a LedgerError and that both bounty.awards_paid and the recipient balance remain unchanged.

Evidence checked:

  • Inspected _clean_proof_metadata() and the later canonical_json(clean_verifier_result) / proof payload serialization path in app/ledger/service.py.
  • Confirmed the new regression only covers non-string metadata keys.
  • Direct-probed this PR head with an object-valued metadata entry and observed the raw TypeError plus in-session awards_paid=1.

No secrets, wallet material, private deployment values, private vulnerability details, or MRWK price claims were used.

Copy link
Copy Markdown

@Thanhdn1984 Thanhdn1984 left a comment

Choose a reason for hiding this comment

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

Reviewed PR #252 on current head.

Summary: no content blockers found for rejecting non-string proof metadata keys. I inspected app/ledger/service.py and tests/test_security.py; _clean_proof_metadata() now rejects non-string keys before formatting the verifier_result.<key> error path, and the regression covers a mixed string/int-key metadata dict without paying the bounty or crediting the recipient.

Local validation:

  • uv run --python 3.12 --extra dev python -m pytest tests/test_security.py::test_bounty_payment_proof_rejects_non_string_metadata_keys -q -> 1 passed
  • uv run --python 3.12 --extra dev ruff check app/ledger/service.py tests/test_security.py -> all checks passed

Caveat: GitHub currently reports no hosted checks for this branch via gh pr checks 252, so I only verified the focused local path and lint, not hosted CI.

No secrets, wallet material, private deployment values, private vulnerability details, payout credentials, or MRWK price claims were used.

@kbaajjg-max kbaajjg-max force-pushed the codex/reject-proof-metadata-keys branch from 9d00327 to b2f5ff2 Compare May 25, 2026 14:00
@kbaajjg-max
Copy link
Copy Markdown
Author

Updated this PR to address the review finding about non-JSON-serializable verifier metadata values.

What changed:

  • Added a pre-mutation canonical JSON serializability check for cleaned verifier_result metadata after the existing accepted_by override.
  • Non-serializable metadata now raises LedgerError("verifier_result must be JSON serializable") before awards_paid, submissions, ledger entries, or proofs are written.
  • Added a regression with verifier_result={"label": "mrwk:accepted", "raw": object()} that asserts the controlled error, awards_paid == 0, and unchanged recipient balance.

Validation:

  • python -m pytest tests/test_security.py::test_bounty_payment_proof_rejects_non_string_metadata_keys tests/test_security.py::test_bounty_payment_proof_rejects_unserializable_metadata_values -q -> 2 passed
  • python -m pytest tests/test_security.py -q -> 34 passed
  • ruff check app/ledger/service.py tests/test_security.py -> passed
  • ruff format --check app/ledger/service.py tests/test_security.py -> passed
  • git diff --check -> clean
  • python -m pytest -q -> 207 passed, 2 warnings

Copy link
Copy Markdown

@Kleenex-ultrasoft Kleenex-ultrasoft left a comment

Choose a reason for hiding this comment

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

No blockers found on the updated head. I specifically rechecked the earlier canonical-JSON gap from AugmentSecurity's review. The current pay_bounty() path now cleans verifier metadata, applies the accepted_by override, and then calls _ensure_json_serializable(clean_verifier_result, "verifier_result") before validate_public_url(), duplicate-submission lookup, award counter mutation, ledger entry creation, or proof creation. That keeps both non-string keys and object-valued metadata on controlled LedgerError paths before payout state can change.

Evidence checked:

  • Inspected app/ledger/service.py around _clean_proof_metadata(), _ensure_json_serializable(), and the start of pay_bounty().
  • Inspected tests/test_security.py regressions for non-string metadata keys, unserializable metadata values, and the existing control-character metadata case.
  • /tmp/mergework-pr265-venv/bin/python -m pytest tests/test_security.py::test_bounty_payment_proof_rejects_non_string_metadata_keys tests/test_security.py::test_bounty_payment_proof_rejects_unserializable_metadata_values tests/test_security.py::test_bounty_payment_proof_rejects_control_character_metadata -q -> 3 passed.
  • /tmp/mergework-pr265-venv/bin/python -m pytest tests/test_security.py -q -> 34 passed.
  • /tmp/mergework-pr265-venv/bin/ruff check app/ledger/service.py tests/test_security.py -> passed.
  • /tmp/mergework-pr265-venv/bin/ruff format --check app/ledger/service.py tests/test_security.py -> 2 files already formatted.
  • /tmp/mergework-pr265-venv/bin/python -m mypy app/ledger/service.py -> success.
  • git diff --check origin/main...HEAD -> clean.

Hosted checks are still unavailable for this branch: gh pr checks reports no checks on codex/reject-proof-metadata-keys.

@ramimbo
Copy link
Copy Markdown
Owner

ramimbo commented May 26, 2026

Closing this stale small-fix PR to clear the filled #228/#292 queue. #292 is now filled, no fresh small-fixes round is open, and this remaining work is not accepted in this pass.

@ramimbo ramimbo closed this May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mrwk:rejected Submission rejected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants