Skip to content

fix(policy,catalog): enforce canonical base64url signatures, strict decimal bundle versions, and duplicate-key rejection (#802) - #807

Open
drk-ram wants to merge 3 commits into
agentrust-io:mainfrom
drk-ram:drk-ram-fix-policy-catalog-802
Open

drk-ram wants to merge 3 commits into
agentrust-io:mainfrom
drk-ram:drk-ram-fix-policy-catalog-802

Conversation

@drk-ram

@drk-ram drk-ram commented Oct 23, 2026 •

Copy link
Copy Markdown

What

Enforce canonical unpadded base64url in cmcp_runtime.policy.bundle._b64url_decode and cmcp_runtime.catalog.approval._decode, require strict ASCII decimal segments in parse_bundle_version, and reject duplicate JSON keys and NaN/Infinity in policy bundle manifests and revocation files.

Why

Refs #802. The static bundled _approval_schema JSON loader remains tracked there as a separate follow-up.

Security impact

Prevents malleable Ed25519 signatures (non-zero unused low padding bits) in policy bundles, key revocations, and catalog-approval records, prevents negative or non-ASCII integers in parse_bundle_version, and blocks duplicate-key JSON smuggling in manifest.json and signing-key-revocations.json.

Test plan

  • pytest passes (pytest tests/unit/test_policy_bundle.py tests/unit/test_policy_signing.py tests/unit/test_policy_key_revocation.py tests/unit/test_catalog_approval.py -q)
  • ruff check passes (ruff check src/cmcp_runtime/policy/bundle.py src/cmcp_runtime/catalog/approval.py tests/unit/test_policy_bundle.py)
  • mypy passes (mypy src/cmcp_runtime/policy/bundle.py src/cmcp_runtime/catalog/approval.py)
  • Manual test performed

DCO sign-off

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

🟡 Contributor Check: MEDIUM

Check Result
Profile MEDIUM
Credential LOW
Overall MEDIUM

Automated check by AgenTrust Contributor Check.

@github-actions github-actions Bot added the needs-review:MEDIUM Contributor check flagged MEDIUM risk label Oct 23, 2026
…ecimal bundle versions, and duplicate-key rejection (agentrust-io#802)

- Enforce canonical unpadded base64url in policy.bundle._b64url_decode and catalog.approval._decode.
- Require ASCII decimal segments in parse_bundle_version and reject duplicate JSON keys and NaN/Infinity in load_policy_bundle and PolicySigningKeys.apply_file.

Closes agentrust-io#802

Signed-off-by: Raghuvar Durga <177662415+drk-ram@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@drk-ram
drk-ram force-pushed the drk-ram-fix-policy-catalog-802 branch from e5ce67a to 0e42f82 Compare October 23, 2026 15:33

@carloshvp carloshvp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed exact head 0e42f829620840e6bf79898f393f87e25b8f7bbc in an isolated checkout.

Requesting changes for a bounded validation finding in the changed security-sensitive path. I submitted the reproducer and a comparison against the actual merge-base behavior through the repository's private vulnerability-reporting channel, following SECURITY.md. Maintainers can find report GHSA-w5v5-4fgp-26x2; it is in triage and unpublished. Please address that report before this head is approved. This is a pre-merge regression report; I have not established released-version impact or additional attacker capability.

Separately, the ordinary local checks pass: 2,532 unit tests passed, 20 skipped in a hash-locked Python 3.12.10 environment; pip check, Ruff, mypy and Bandit passed. All commits have sign-offs. Those passing checks do not cover the privately reported control. git diff --check also flags an extra blank line at test_policy_bundle.py EOF.

Reviewed with OpenAI Codex.

@noah-ing noah-ing left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed exact head 0e42f829620840e6bf79898f393f87e25b8f7bbc. My independent signed before/after replay confirms the validation regression already reported to maintainers through the private security channel. Requesting changes until that finding is fixed and covered by a targeted regression test; please coordinate the reproducer privately with the maintainers. I am keeping its mechanics out of this public review.

The ordinary exact-head unit suite passed (2,532 passed, 20 skipped), but it does not exercise the reported case. I saw no substantive hosted CI result for this head; the maintainer gate remains outstanding. This review does not assert released-version impact.

…ment in apply_file

Signed-off-by: Raghuvar Durga <177662415+drk-ram@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@drk-ram

drk-ram commented Oct 24, 2026

Copy link
Copy Markdown
Author

Thanks @carloshvp and @noah-ing! Fixed the pre-merge regression in PolicySigningKeys.apply_file (src/cmcp_runtime/policy/bundle.py): a statement in signing-key-revocations.json containing duplicate JSON keys or NaN/Infinity now marks only that statement with _INVALID_REVOCATION_JSON (rejected individually in PolicySigningKeys.apply with PolicyKeyRevocationInvalid) rather than aborting json.loads() for the entire revocation file and blocking valid signed revocation statements in the same file. Added test_revocation_file_rejects_duplicate_key_statement_without_blocking_valid_revocation in tests/unit/test_policy_bundle.py and trimmed the EOF blank line.

@noah-ing

Copy link
Copy Markdown
Collaborator

Thanks for the update. I reviewed exact head 5e9a8c00ba4d0ccc3ecb0e0dd0578b9835208a96. The original signed mixed-statement regression is fixed: the later valid revocation now applies, and the new committed test distinguishes the merge base from the previous PR head. Focused tests passed (58/58); the full local unit suite passed (2,533 passed, 20 skipped).

One narrow strict-JSON case remains. _revocation_object_pairs propagates invalid markers through a list only one level deep. A valid signed statement with an ignored extra field containing nested arrays around a duplicate-key object or NaN is accepted by apply_file, although #802 calls for duplicate/non-finite rejection in revocation JSON. I found no authority escalation or suppression of another valid revocation through this case. Please reject invalid markers recursively through nested arrays and add signed negative controls. Ruff also fails I001 on the new import block at tests/unit/test_policy_bundle.py:451. I am keeping the changes request open for these focused corrections. Hosted substantive CI has not run on this head.

Signed-off-by: Srinivasa Consultants <78673153+dinakarjs@users.noreply.github.com>

@dinakarjs dinakarjs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I prepared a signed adoption companion addressing the outstanding review finding: dinakarjs#7

Validation: source repair unit suite 2547 passed/6 skipped; conformance+integration+confinement 114 passed/23 skipped. Final test-only parameterization rerun: all 37 policy-bundle tests pass. Its four nested-array cases each fail by assertion when the source repair is removed, with test names retained. Ruff src/tests, strict mypy and Bandit passed. No claim of authorization escalation: this closes strict-JSON acceptance.

Please cherry-pick/adopt the companion and request independent re-review of the resulting head. I have contributed the follow-up and am not approving my own contribution.

@drk-ram

drk-ram commented Oct 24, 2026

Copy link
Copy Markdown
Author

Thanks @noah-ing! Added _contains_invalid_revocation to walk nested arrays recursively in _revocation_object_pairs, added signed negative controls for nested arrays around duplicate-key objects and NaN in tests/unit/test_policy_bundle.py, and fixed the I001 import sorting.

@drk-ram

drk-ram commented Oct 25, 2026

Copy link
Copy Markdown
Author

Thanks @dinakarjs and @noah-ing! Adopted @dinakarjs's signed companion commit 6546592 (fix/review-807-20261025) directly as the branch head with authorship and signature preserved.

@drk-ram
drk-ram force-pushed the drk-ram-fix-policy-catalog-802 branch from 17a2d5b to 6546592 Compare October 25, 2026 04:48

@noah-ing noah-ing left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed exact head 654659216ece77f4e47bc78eb98aca703b8849f4. The signed revocation-row regression and nested invalid-JSON case from my prior review now behave correctly in independent before/after controls: an invalid row is rejected without suppressing the later valid signed row. The focused policy-bundle tests passed (37/37); the full unit suite passed (2,536 passed, 20 skipped), and Ruff, changed-source mypy, dependency and whitespace checks passed. This resolves my requested changes, so I approve this scoped patch.

Nonblocking issue tracking: #802’s summary also names _approval_schema, whose static bundled schema loader still uses permissive JSON parsing. I found no authority effect from that path. Please track it separately or adjust the Closes #802 reference before merge so it is not lost when the issue closes. Substantive hosted CI still needs to run under the normal gate.

@carloshvp carloshvp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed exact head 654659216ece77f4e47bc78eb98aca703b8849f4 in an isolated checkout.

My previous pre-merge validation finding is addressed by the adopted repair at this head. I independently replayed the privately reported case against both the previously reviewed source and the current source, with valid signed controls and rejection controls. The current implementation passes those controls while preserving rejection of an ambiguous signed statement. Details of the originally reported case remain in unpublished report GHSA-w5v5-4fgp-26x2; this public review omits the reproducer.

In a fresh hash-locked Python 3.12.10 environment, the full unit suite passed: 2,536 passed, 20 skipped. pip check, Ruff, mypy and configured Bandit also passed; the unit run used the loopback permissions needed by its local transport fixtures. Commit sign-offs are present. This supersedes my earlier code changes request at the older head. The private report remains unpublished/in triage, and required security-maintainer approvals, hosted CI and any advisory/release disposition remain separate. No released-version impact is asserted by this review.

Reviewed with OpenAI Codex.

@carloshvp

Copy link
Copy Markdown
Member

Private-report follow-up at unchanged head 654659216ece77f4e47bc78eb98aca703b8849f4: I reran the original paired control against this head and the previously reported source. The adopted repair still passes; the older source still fails the reported control. This agrees with the existing independent repair reviews. Reproduction details remain in GHSA-w5v5-4fgp-26x2.

The report remains in triage because GitHub returned HTTP 403 when I tried to record its resolved pre-merge disposition: this account lacks the security-advisory triage permission. An authorized repository security triager should disposition that private report using the repair evidence. This is not a claim about a released affected version. The PR still needs its normal merge/CI gates.

I also changed the description to Refs #802, preserving the issue for the separately noted static _approval_schema loader follow-up. No source or reviewer approval was changed.

Reviewed with OpenAI Codex.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review:MEDIUM Contributor check flagged MEDIUM risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants