Skip to content

fix(authz): accept bounded Keyverse claim aliases - #58

Open
seonghobae wants to merge 13 commits into
mainfrom
codex/keyverse-oidc-claim-aliases
Open

fix(authz): accept bounded Keyverse claim aliases#58
seonghobae wants to merge 13 commits into
mainfrom
codex/keyverse-oidc-claim-aliases

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • accept Keyverse org as the tenant claim and map singular bounded role values
  • keep unknown roles deny-by-default and preserve existing group RBAC/ABAC policy
  • add signed-JWKS and claim-shape regressions plus docs

Verification

  • PYTHONPATH=src .venv/bin/python -m pytest -q (248 passed, 8 skipped)

This implements the immediate application fix recorded in Keyverse ADR-0008.

Summary by CodeRabbit

  • 새로운 기능

    • OIDC 인증 시 Keyverse 조직 정보를 테넌트로 매핑합니다.
    • Keyverse 역할을 제한된 SDP RBAC 역할로 변환하고 그룹 역할과 함께 적용합니다.
    • 인식되지 않은 역할은 권한에서 제외합니다.
  • 버그 수정

    • 잘못된 역할 형식, 충돌하는 테넌트 정보, 지원되지 않는 JWT 중요 헤더를 거부합니다.
  • 문서

    • OIDC 토큰 검증 및 테넌트·역할 매핑 설명을 보완했습니다.
  • 테스트

    • 조직·역할 매핑과 예외 처리 시나리오를 검증하는 테스트를 추가했습니다.

Current head: 0b40e77

  • validate every present Keyverse tenant alias and reject malformed/conflicting aliases before ActorContext
  • explicitly reject unsupported JWT crit headers because this verifier supports no critical extensions
  • focused authz/API tests, full pytest suite (8 integration tests skipped), changed-file Ruff F/E9, compileall, and diff check passed
  • current-head Strix review was remediated with the fail-closed critical-header boundary; use the current Checks as release evidence

Open in Devin Review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 47a8d143-5b92-410c-8637-5da49db7aed7

📝 Walkthrough

Walkthrough

OIDC 검증이 Keyverse orgrole 클레임을 ActorContext의 tenant와 제한된 SDP RBAC role로 매핑합니다. 잘못된 형식과 충돌하는 tenant 클레임 및 지원되지 않는 JWT critical header를 거부합니다. 테스트, 문서, Python 의존성을 갱신합니다.

Changes

Keyverse OIDC 매핑

Layer / File(s) Summary
테넌트 및 역할 클레임 매핑
src/sdp/authz.py, tests/test_authz.py
테넌트 후보를 tenant_id, tid, organization, org로 확장합니다. 단일 문자열 role만 허용합니다. Keyverse 역할을 SDP RBAC role로 변환하고 그룹 역할과 병합합니다. 잘못된 값과 충돌을 거부하고 미인식 역할은 무시합니다.
JWT 헤더 및 API 검증
src/sdp/authz.py, tests/test_api.py
지원되지 않는 critical JWT header를 거부합니다. OIDC 검증 엔드포인트가 해당 오류에 HTTP 400을 반환하는지 검증합니다.
문서 및 실행 의존성 갱신
README.md, docs/implementation-compliance.md, pyproject.toml, requirements*.txt
Keyverse 클레임 매핑 동작을 문서에 반영합니다. cryptography를 50.0.0으로 갱신하고 잠금 파일과 관련 의존성을 수정합니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested labels: dependencies, python

Sequence Diagram(s)

sequenceDiagram
  participant Client as OIDC 클라이언트
  participant Verify as oidc-verify 엔드포인트
  participant JWKS as JWKS 검증
  participant Authz as authz 매핑
  participant Context as ActorContext
  Client->>Verify: 서명된 토큰 제출
  Verify->>JWKS: 서명 및 JWT header 검증
  JWKS->>Authz: 검증된 org, workspace, role 전달
  Authz->>Authz: tenant 후보 검증 및 RBAC role 매핑
  Authz->>Context: tenant_id 및 roles 설정
  Context-->>Verify: 검증 결과 반환
  Verify-->>Client: ActorContext 응답
Loading

✅ Pre-merge checks override applied

The pre-merge checks have been overridden successfully. You can now proceed with the merge.

Overridden by @seonghobae via command on 2026-08-11T12:15:32.969Z.

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 Keyverse 클레임 별칭을 제한적으로 허용하는 주요 권한 부여 변경을 정확하고 간결하게 설명합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/keyverse-oidc-claim-aliases

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

coderabbitai[bot]

This comment was marked as resolved.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent review

Please review exact current head 8bf7df4e025d1ec208b277129dd5db750eccac0c: bounded Keyverse claim aliases plus the cryptography 50.0.0 CVE remediation and regenerated hash locks. Re-evaluate only this head after checks complete.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent @cwl-noema-review

Formal review-only request for exact current head 8bf7df4e025d1ec208b277129dd5db750eccac0c: bounded Keyverse claim mapping plus cryptography 50.0.0 CVE remediation are complete; wait for remaining security/fuzz checks, then submit an independent GitHub review for this exact head. Do not mutate or merge.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent @cwl-noema-review

Review exact current head 773964ebf6ec33ea0eacab876a66dfda1069bf2f only. This head includes bounded Keyverse claim aliases, single-string role enforcement with array denial tests, full allowed-role/group coverage, and cryptography 50.0.0 CVE remediation. Do not carry predecessor findings forward.

@seonghobae
seonghobae enabled auto-merge (squash) August 11, 2026 11:37
@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review the current head 46b9fdb. The Keyverse claim-alias, fail-closed malformed-role, and cryptography security fixes are complete; full tests and CI/security checks pass.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

@seonghobae: I will review pull request #58 at the current head 46b9fdb. I will evaluate the Keyverse claim mapping and the cryptography remediation.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent review

Please submit an independent review for exact current head 46b9fdb. All current CI, security, fuzz, coverage, and CodeRabbit checks are successful; predecessor comments were addressed. Do not mutate the branch or merge.

@seonghobae
seonghobae disabled auto-merge August 11, 2026 12:07
@seonghobae
seonghobae enabled auto-merge (squash) August 11, 2026 12:07
@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai help

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

CodeRabbit commands

These commands are invoked using PR/Issue comments.

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai rate limit to show your current review rate limit status.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai evaluate custom pre-merge check --instructions <custom-checks-instructions> --name <custom-checks-title> [--mode <error|warning>] to validate the custom pre-merge checks instructions. Defaults to error when --mode is omitted.
  • @coderabbitai ignore pre-merge checks to override pre-merge checks and get an approval on PR.
  • @coderabbitai run pre-merge checks to run pre-merge checks on the pull request.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai emit path instructions to emit generated path-specific review instructions for this repository.
  • @coderabbitai resolve merge conflict to automatically resolve merge conflicts.
  • @coderabbitai autofix to automatically fix issues identified in unresolved review comments.
  • @coderabbitai fix-ci to automatically fix failing CI checks in a stacked pull request.
  • @coderabbitai fix-ci commit to automatically fix failing CI checks by committing fixes to the current branch.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai generate configuration to create a PR that adds the current resolved configuration as .coderabbit.yaml (or show it if already present).
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit configuration file (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, support, documentation and community

  • Visit our status page to check the current availability of CodeRabbit.
  • Create a ticket on our support page for assistance with any issues or questions.
  • Visit our documentation site for detailed information on how to use CodeRabbit.
  • Join our Discord community to connect with other users and get help from the community.
  • Follow us on X/Twitter for updates and announcements.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai run pre-merge checks

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
✅ Actions performed

Running Pre-merge checks. Results will be posted in the summary comment once complete.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai ignore pre-merge checks

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
ℹ️ Actions performed

Pre-merge checks override executed successfully.

Overridden by @seonghobae via command on 2026-08-11T12:15:32.969Z.

Approval outcome: Approval skipped: request-changes workflow disabled.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

Please review exact current head 46b9fdb4480c665f6f513acf4edfdb5848ca64. Recheck the Keyverse org/role aliases, malformed and multi-valued claim fail-closed behavior, and the cryptography floor. Do not mutate or merge.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

@seonghobae I will perform a full review of #58 at 46b9fdb4480c665f6f513acf4edfdb5848ca64. I will check the Keyverse claim mapping, fail-closed role-claim handling, and the cryptography version floor. I will not mutate or merge the branch.

✅ Action performed

Full review finished.

coderabbitai[bot]

This comment was marked as resolved.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact current head a93f9ed after the tenant-claim fix. Verify array/object/blank org claims fail closed before ActorContext and that the existing Keyverse role/cryptography protections remain intact. Do not mutate or merge.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

@seonghobae I will review pull request #58 at a93f9ed69ba569f3379915ceb20b95b7fcb4a41c.

I will verify that array, object, and blank org claims fail closed before ActorContext creation. I will also recheck the Keyverse role claim protections and the cryptography version floor. I will not modify or merge the branch.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact current head a93f9ed after the tenant-claim fix. Verify array/object/blank org claims fail closed before ActorContext and that existing Keyverse role and cryptography protections remain intact. Do not mutate or merge.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

@seonghobae I will review pull request #58 at a93f9ed69ba569f3379915ceb20b95b7fcb4a41c.

I will verify that array, object, and blank org claims fail closed before ActorContext creation. I will also verify the Keyverse role claim protections and the cryptography version floor. I will not modify or merge the branch.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor Author

@opencode-agent review

Please perform an independent formal review of exact head a93f9ed69ba569f3379915ceb20b95b7fcb4a41c. All four repository workflows (Tests, fuzz, Security Scan, SAST Semgrep) are terminal-success on this SHA and every current CodeRabbit thread is resolved. Validate the bounded Keyverse claim-alias mapping, fail-closed claim-shape handling, regression coverage, and dependency-lock consistency against this exact head; do not transfer predecessor-head evidence.

Strix flagged the preferred_username/email/sub or-chain as subject
injection because a signed non-string claim was coerced into
ActorContext.subject. Require a non-empty string alias, keep JSON-null
fall-through, and map every JWT crit failure to one application error
without classifying PyJWT exception text.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
@cursor

cursor Bot commented Aug 17, 2026

Copy link
Copy Markdown

@opencode-agent review

Please submit an independent review for exact current head 80966aede42682b993f3f269a543e96f07e92514 only. The previous CHANGES_REQUESTED was because Strix failed on 9348983803fd6dda5d77e0ded80ab9ee3d72bf06. Current-head Strix Security Scan is SUCCESS (https://github.com/ContextualWisdomLab/semantic-data-portal/actions/runs/32046440122). Subject aliases now fail closed on non-string or blank values; JWT crit failures map to one application error without classifying PyJWT exception text. Do not mutate or merge.

@cursor
cursor Bot enabled auto-merge (squash) August 17, 2026 17:19
@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai review
@opencode-agent review

Please review exact current head 80966aede42682b993f3f269a543e96f07e92514 only. Previous OpenCode CHANGES_REQUESTED was on stale SHA 9348983803fd6dda5d77e0ded80ab9ee3d72bf06 because Strix failed there. Current-head required checks including Strix are green. Subject aliases fail closed on non-string or blank values; JWT crit failures map to one application error. Do not mutate or merge.

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

expected='80966aede42682b993f3f269a543e96f07e92514'
actual="$(git rev-parse HEAD)"
printf 'HEAD=%s\nEXPECTED=%s\n' "$actual" "$expected"
test "$actual" = "$expected"

echo '== Changed files and whitespace =='
git diff --name-only "${expected}^" "$expected"
git diff --check "${expected}^" "$expected"

echo '== Authorization module outline =='
ast-grep outline src/sdp/authz.py --items all --view expanded

echo '== Subject, tenant, role, JWT header, and ActorContext paths =='
rg -n -C 10 --glob 'src/sdp/authz.py' \
  '(sub|subject|crit|header|JWT|jwt|decode_complete|get_unverified_header|tenant_id|organization|["'\'']org["'\'']|["'\'']role["'\'']|ActorContext|resolve_oidc_actor_context|verify_oidc)' .

echo '== Current-head authorization and regression-test patch =='
git diff --find-renames --find-copies --unified=100 "${expected}^" "$expected" -- \
  src/sdp/authz.py tests/test_authz.py tests/test_api.py

echo '== Focused regression tests =='
rg -n -C 10 --glob 'tests/test_authz.py' --glob 'tests/test_api.py' \
  '(sub|subject|crit|header|malformed|tenant_id|organization|["'\'']org["'\'']|["'\'']role["'\'']|ValueError|ActorContext|oidc|OIDC|jwks|JWKS)' .

echo '== Direct callers of verification and claim-resolution functions =='
rg -n -C 4 --glob '*.py' \
  '\b(verify_oidc_jwks_token|resolve_oidc_actor_context|validate_oidc_claim_shape|_tenant_claim_value|get_unverified_header|decode_complete)\s*\(' .

echo '== Dependency inputs and lock entries =='
for file in pyproject.toml requirements-test.in requirements.txt requirements-test.txt requirements-dev.txt; do
  if [ -f "$file" ]; then
    echo "--- $file"
    rg -n -C 2 '^cryptography==50\.0\.0$|^cryptography==' "$file" || true
  fi
done

echo '== Lock entry versions and hashes =='
python - <<'PY'
from pathlib import Path
import re
for name in ("requirements.txt", "requirements-test.txt", "requirements-dev.txt"):
    path = Path(name)
    if not path.exists():
        continue
    entry = re.search(
        r'(?ms)^cryptography==([^\s\\]+)\s*\\\n((?:\s+--hash=[^\n]+(?:\s*\\\n)?)+)',
        path.read_text(),
    )
    print(
        f"{name}: version={entry.group(1) if entry else 'MISSING'}, "
        f"hashes={'present' if entry else 'MISSING'}"
    )
PY

echo '== Required GitHub checks for PR `#58` =='
gh pr checks 58 --repo ContextualWisdomLab/semantic-data-portal --required

Length of output: 136930


🏁 Script executed:

#!/bin/bash
set -euo pipefail

expected='80966aede42682b993f3f269a543e96f07e92514'
test "$(git rev-parse HEAD)" = "$expected"

echo '== Tracked dependency inputs and cryptography declarations =='
git ls-files | rg '(^|/)(pyproject\.toml|requirements[^/]*\.(in|txt))$' | sort
rg -n -C 3 --glob 'pyproject.toml' --glob 'requirements*.in' --glob 'requirements*.txt' \
  '(^|\s)cryptography([<>=!~ ]|$)|PyJWT' .

echo '== pyproject dependency configuration =='
sed -n '1,240p' pyproject.toml

echo '== OIDC API error conversion =='
ast-grep outline src/sdp/api.py --items all --view expanded
sed -n '200,280p' src/sdp/api.py | cat -n

echo '== Documentation patch at requested SHA =='
git diff --unified=40 "${expected}^" "$expected" -- README.md docs/implementation-compliance.md

echo '== GitHub check runs attached to the requested commit =='
gh api \
  "repos/ContextualWisdomLab/semantic-data-portal/commits/${expected}/check-runs?per_page=100" \
  --jq '.check_runs[] | [.name, .status, .conclusion, .html_url] | `@tsv`'

echo '== GitHub status contexts attached to the requested commit =='
gh api \
  "repos/ContextualWisdomLab/semantic-data-portal/commits/${expected}/status" \
  --jq '.statuses[]? | [.context, .state, .target_url] | `@tsv`'

Length of output: 30060


@seonghobae I reviewed exact head 80966aede42682b993f3f269a543e96f07e92514.

I found no actionable issues in the requested scope.

  • Subject aliases reject present non-string and blank values before ActorContext creation.
  • JSON-null subject aliases can fall through to a later alias. All-null aliases fail closed.
  • A numeric sub cannot become a string subject.
  • JWT headers are checked before JWK selection and claim decoding.
  • Unsupported and malformed crit values use the same critical-header rejection path.
  • Tenant aliases still validate before tenant selection.
  • Bounded Keyverse role handling remains fail closed. Unknown roles grant no application roles.
  • cryptography==50.0.0 is consistent in dependency inputs and hash lock files.
  • git diff --check found no whitespace errors.
  • Check runs on this SHA, including strix, security scans, fuzz/property tests, API integration, and SAST checks, passed.

This was a static review. I did not run repository tests in the sandbox. I did not modify or merge the branch.

You are interacting with an AI system.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent review

@cursor
cursor Bot disabled auto-merge August 17, 2026 20:40
@cursor
cursor Bot enabled auto-merge (squash) August 17, 2026 20:40
@cursor
cursor Bot marked this pull request as draft August 17, 2026 20:45
auto-merge was automatically disabled August 17, 2026 20:45

Pull request was converted to draft

@cursor
cursor Bot marked this pull request as ready for review August 17, 2026 20:45
@cursor
cursor Bot enabled auto-merge (squash) August 17, 2026 20:45
@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact current head 80966aede42682b993f3f269a543e96f07e92514 only.

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

expected='80966aede42682b993f3f269a543e96f07e92514'
actual="$(git rev-parse HEAD)"
printf 'HEAD=%s\nEXPECTED=%s\n' "$actual" "$expected"
test "$actual" = "$expected"

echo '== Changed files and diff whitespace =='
git diff --name-only "${expected}^" "$expected"
git diff --check "${expected}^" "$expected"

echo '== Authorization module structure =='
ast-grep outline src/sdp/authz.py --items all --view expanded

echo '== Authentication and claim-validation paths =='
rg -n -C 12 --glob 'src/sdp/authz.py' \
  '(verify_oidc_jwks_token|_token_header_has_critical_extensions|crit|get_unverified_header|decode_complete|validate_oidc_claim_shape|_tenant_claim_value|_subject_claim_value|resolve_oidc_actor_context|ActorContext|tenant_id|organization|["'"'"']org["'"'"']|["'"'"']role["'"'"'])' .

echo '== Exact commit patch: authorization and tests =='
git diff --find-renames --find-copies --unified=100 "${expected}^" "$expected" -- \
  src/sdp/authz.py tests/test_authz.py tests/test_api.py src/sdp/api.py

echo '== Focused regression coverage =='
rg -n -C 10 --glob 'tests/test_authz.py' --glob 'tests/test_api.py' \
  '(sub|subject|crit|header|malformed|tenant_id|organization|["'"'"']org["'"'"']|["'"'"']role["'"'"'"']|None|blank|conflict|ValueError|ActorContext|oidc|OIDC|jwks|JWKS)' .

echo '== Direct callers =='
rg -n -C 4 --glob '*.py' \
  '\b(verify_oidc_jwks_token|resolve_oidc_actor_context|validate_oidc_claim_shape|_tenant_claim_value|_subject_claim_value|_token_header_has_critical_extensions)\s*\(' .

echo '== cryptography inputs and lock entries =='
for file in pyproject.toml requirements-test.in requirements.txt requirements-test.txt requirements-dev.txt; do
  if [ -f "$file" ]; then
    echo "--- $file"
    rg -n -C 2 '^cryptography==50\.0\.0$|^cryptography==' "$file" || true
  fi
done

echo '== cryptography lock entry integrity =='
python - <<'PY'
from pathlib import Path
import re
for name in ("requirements.txt", "requirements-test.txt", "requirements-dev.txt"):
    path = Path(name)
    if not path.exists():
        continue
    entry = re.search(
        r'(?ms)^cryptography==([^\s\\]+)\s*\\\n((?:\s+--hash=[^\n]+(?:\s*\\\n)?)+)',
        path.read_text(),
    )
    print(f"{name}: version={entry.group(1) if entry else 'MISSING'}, hashes={'present' if entry else 'MISSING'}")
PY

echo '== Check runs for the exact commit =='
gh api "repos/ContextualWisdomLab/semantic-data-portal/commits/${expected}/check-runs?per_page=100" \
  --jq '.check_runs[] | [.name, .status, .conclusion] | `@tsv`'

Length of output: 54779


🏁 Script executed:

#!/bin/bash
set -euo pipefail

expected='80966aede42682b993f3f269a543e96f07e92514'
test "$(git rev-parse HEAD)" = "$expected"

echo '== Focused API regressions =='
rg -n -C 8 --glob 'tests/test_api.py' \
  'crit|critical|malformed|oidc|OIDC|jwks|JWKS|subject|preferred_username' tests/test_api.py

echo '== Focused authorization regressions =='
rg -n -C 8 --glob 'tests/test_authz.py' \
  'crit|critical|malformed|oidc|OIDC|jwks|JWKS|subject|preferred_username|tenant_id|organization|org|role|blank|conflict' tests/test_authz.py

echo '== OIDC API exception conversion =='
ast-grep outline src/sdp/api.py --items all --view expanded
rg -n -C 10 --glob 'src/sdp/api.py' \
  'oidc-verify|oidc-preview|verify_oidc_jwks_token|resolve_oidc_actor_context|ValueError|HTTPException' src/sdp/api.py

echo '== Dependency inputs and lock entries =='
for file in pyproject.toml requirements-test.in requirements.txt requirements-test.txt requirements-dev.txt; do
  if [ -f "$file" ]; then
    echo "--- $file"
    rg -n -C 2 '^cryptography==50\.0\.0$|^cryptography==' "$file" || true
  fi
done

echo '== cryptography lock entry integrity =='
python - <<'PY'
from pathlib import Path
import re
for name in ("requirements.txt", "requirements-test.txt", "requirements-dev.txt"):
    path = Path(name)
    entry = re.search(
        r'(?ms)^cryptography==([^\s\\]+)\s*\\\n((?:\s+--hash=[^\n]+(?:\s*\\\n)?)+)',
        path.read_text(),
    )
    print(f"{name}: version={entry.group(1) if entry else 'MISSING'}, hashes={'present' if entry else 'MISSING'}")
PY

echo '== Check runs for the exact commit =='
gh api "repos/ContextualWisdomLab/semantic-data-portal/commits/${expected}/check-runs?per_page=100" \
  --jq '.check_runs[] | [.name, .status, .conclusion] | `@tsv`'

Length of output: 65031


@seonghobae I reviewed exact head 80966aede42682b993f3f269a543e96f07e92514.

I found no actionable issues.

  • Subject aliases reject present non-string and blank values before ActorContext creation.
  • JSON-null subject aliases can fall through to a later alias. All-null aliases fail closed.
  • Tenant validation checks every present alias before tenant selection.
  • Null, non-string, blank, and conflicting tenant aliases fail closed.
  • The singular Keyverse role claim rejects malformed values. Unknown roles grant no application roles.
  • JWT headers with crit fail before JWK selection and claim decoding.
  • Unsupported and malformed crit values map to the same application error.
  • API regression tests cover both critical-header cases.
  • The inspected lock files use cryptography==50.0.0 and include hashes.
  • git diff --check found no whitespace errors.
  • GitHub checks on this commit succeeded, including strix, API integration, SAST, dependency review, vulnerability scans, and fuzz/property tests.

This was a static review. I did not modify or merge the branch.

You are interacting with an AI system.

@opencode-agent opencode-agent Bot added priority: medium Normal-priority or P2 work status: needs-review Open pull request requiring current-head review or checks type: bug Defect or incorrect behavior labels Aug 22, 2026
opencode-agent[bot]
opencode-agent Bot previously approved these changes Aug 24, 2026

@opencode-agent opencode-agent Bot 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.

Pull request overview

OpenCode reviewed the current-head bounded evidence and found no blocking issues.

Findings

No blocking findings.

Summary

Reviewed PR #58 'fix(authz): accept bounded Keyverse claim aliases' at head 80966ae using the trusted current-head bounded evidence (changed files, focused hunks for README.md, docs/implementation-compliance.md, pyproject.toml, requirements-dev.txt), CodeGraph current-head exploration (index up to date, 65 files, changed-scope exploration over all 10 changed paths), Coverage execution evidence, and Failed GitHub Check evidence. Changed files inspected as changed-file evidence: src/sdp/authz.py, tests/test_api.py, tests/test_authz.py, pyproject.toml, requirements-dev.txt, requirements-test.in, requirements-test.txt, requirements.txt, README.md, docs/implementation-compliance.md. Approval sufficiency: sufficient — authoritative current-head sections report no completed failed GitHub Checks, no unresolved non-outdated review threads, and Coverage Decision Result PASS. Verification posture: repository-native pytest suite green (Coverage execution evidence PASS; PR-reported PYTHONPATH=src .venv/bin/python -m pytest -q 248 passed, 8 skipped treated as author claim corroborated by trusted coverage evidence). Linter/static: no active failed static/SAST checks at this head; repo security commands (pip_audit, bandit, trivy fs) reported no completed failures. TDD/regression: yes — the diff adds targeted regressions named in docs/implementation-compliance.md (test_oidc_preview_rejects_non_string_subject_claim, test_subject_claim_rejects_non_string_or_blank_values, test_numeric_sub_claim_cannot_impersonate_string_subject) matching +103/+243 test-line diff stat. Coverage: cites Coverage execution evidence — Result PASS, supported repository test suites passed (pytest via python3 -m pytest tests / coverage --fail-under=100 contract). Docstring coverage: cites Coverage execution evidence — configured repository docstring gates passed or docstring coverage was advisory. DAG: CodeGraph/source-backed flowchart below reflects head flow for src/sdp/authz.py claim mapping feeding ActorContext/policy evaluation. PoC/execution: adversarial probes used trusted diff + Coverage execution evidence outcomes; no browser/E2E receipts claimed because none exist and the surface is API-only. DDD/domain: change confined to the authz domain boundary; catalog/policy/graph domains untouched per CodeGraph blast radius (flagged untested symbols review_patch/build_steward_review_summary are pre-existing, unchanged here). CDD/context: no cross-context coupling introduced; OIDC claim handling stays inside sdp.authz consumed by api.py routes. Similar issues: prior branch history shows iterative hardening commits (malformed tenant claims, conflicting aliases, multi-valued roles, crit headers) — this head completes the series with subject-claim shape enforcement. Claim/concept check: Keyverse org/role alias mapping and fail-closed claim-shape behavior verified against current-head diff and its named regressions; workspace claim explicitly documented as authentication-shape-only. Standards search: not executed in this isolated environment; no external standard claims were material to the verdict beyond OWASP-style fail-closed defaults already encoded in the tested behavior. Compatibility/convention: additive dependency pin and claim acceptance only; unknown roles remain deny-by-default preserving existing group RBAC/ABAC; no renamed API fields, DB objects, or reserved-word/naming risks introduced (no externally meaningful new identifiers beyond documented env vars SDP_OIDC_* already established). Breaking-change/backcompat: tokens lacking org/role continue through group allow-list mapping; stricter subject-claim shape rejection is intentional fail-closed hardening with regression coverage. Implementation completeness: no placeholder bodies introduced; docs claims map 1:1 to added regression tests and the +104/-line authz change per diff stat. Performance: negligible — claim-shape validation runs once per verified token. Developer experience: DX surface is setup/docs; README install/run instructions remain accurate with the new pin mirrored into hash-pinned dev requirements. User experience: UX surface is API consumer behavior; malformed tokens now receive a single classified application error instead of PyJWT exception text leakage. Visual/DOM: non-web interaction surface reviewed (API + docs); no DOM changes. Accessibility/i18n: not applicable to this API-only surface; Korean docs prose updated consistently. Supply-chain/license: cryptography==50.0.0 pinned in pyproject.toml and hash-pinned across requirements-dev.txt/requirements.txt/requirements-test.txt locks (consistent multi-lock propagation visible in diff stat); pip_audit/trivy reported no completed failures at this head. Packaging: pyproject dependencies consistent with locked requirement files; unpackaged_source_surfaces is empty. Security/privacy: raw token never returned; numeric/array/blank subject and tenant aliases fail closed; unknown roles deny-by-default; JWT crit failures normalized to one application error — each backed by named regressions and Coverage execution evidence PASS.

Approval sufficiency: bounded evidence supplied affirmative approval evidence for changed files, coverage/docstring posture, risk surfaces, and current-head verification; approval is not based merely on the absence of known blockers.
Verification posture: CodeGraph evidence was initialized and bounded current-head evidence reviewed for changed-file evidence including README.md, docs/implementation-compliance.md, pyproject.toml, requirements-dev.txt, requirements-test.txt, and 4 more.
Linter/static: workflow/static review evidence is bounded by the current-head GitHub Checks gate and changed-file evidence.
TDD/regression: coverage execution evidence and focused changed hunks were reviewed from bounded-review-evidence.md.
Coverage: coverage execution evidence reports supported repository test suites passed.
Docstring coverage: coverage execution evidence reports configured repository docstring gates passed or docstring coverage was advisory.
DAG: CodeGraph/source-backed behavior map connects README.md to the affected review, runtime, or workflow path and required checks.
PoC/execution: coverage-evidence job executed on the current head and reported PASS.
DDD/domain: workflow and repository-governance invariants were reviewed against changed files in bounded evidence.
CDD/context: CodeGraph evidence, changed-file history, and focused hunks were reviewed from bounded-review-evidence.md.
Similar issues: changed-file history evidence was reviewed for comparable local precedents.
Claim/concept check: bounded evidence, repository source, current-head workflow evidence, and, where numeric, scientific, statistical, or literature-backed claims are affected, original-paper/formula evidence and parameter-recovery expectations were used for claims.
Standards search: standards and external-source claims require trusted bounded source evidence prepared outside the isolated model process; no evidence-backed standards blocker is present in bounded evidence.
Compatibility/convention: changed workflow/script conventions, object naming, and reserved-word safety for schema/API/config/code surfaces were checked in bounded evidence.
Breaking-change/backcompat: deployment evidence and changed-file history were checked for backward-compatibility risk.
Performance: changed surfaces were checked for performance risk in bounded evidence.
Developer experience: changed automation, review, test, setup, and maintenance surfaces were checked for helpful or obstructive DX impact in bounded evidence.
User experience: connected user, operator, API, CLI, documentation, review-comment, status-check, rendering, and workflow-reader behavior was checked for contradictions against code, docs, and tests in bounded evidence.
Visual/DOM: deterministic repair does not infer browser runtime execution; source-backed DOM/UI evidence and trusted workflow receipts were reviewed when present, and non-web surfaces used API/CLI/log/docs/workflow evidence instead.
Accessibility/i18n: accessibility, localization, and human-readable text surfaces were checked where UI, CLI, API message, docs, logs, or review text changed.
Supply-chain/license: dependency, package, model, container, and external-tool changes were checked in bounded evidence.
Packaging: package, build, test, lint, and security contracts were checked in bounded evidence.
Security/privacy: workflow-token, review-gate, and repository-automation security/privacy boundaries were checked in bounded evidence.

Adversarial validation

{"status":"passed","probes":[{"path":"docs/implementation-compliance.md","line":122,"hypothesis":"Non-string, array, or blank subject/preferred_username/email claims could pass POST /enterprise/auth/oidc-verify and produce an ActorContext from malformed identity data (fail-open).","attack_or_counterexample":"Signed-JWKS token presenting sub/preferred_username/email as 12345 (number), ['a','b'] (array), or '' (blank) after otherwise-valid signature/issuer/audience/expiry.","evidence":"Trusted current-head diff hunk at docs/implementation-compliance.md:122 observed the documented contract 'preferred_username/email/sub는 비어 있지 않은 문자열만 허용하며, 숫자·배열·공백 subject claim은 fail-closed로 거부한다' and the same trusted diff adds the regressions tests/test_api.py::test_oidc_preview_rejects_non_string_subject_claim and tests/test_authz.py::test_subject_claim_rejects_non_string_or_blank_values; Coverage execution evidence reports Result PASS (supported repository test suites passed at this head), so the counterexample shapes are rejected rather than accepted; Trusted current-head source binding at docs/implementation-compliance.md:122; source-line-sha256=ffc203d569ef8c3a9f5c4c0144f5e0ebf1e67a65721c5f7a59af9f81c10d2f47","outcome":"falsified"},{"path":"docs/implementation-compliance.md","line":135,"hypothesis":"A numeric sub claim could type-coerce onto an existing string subject (str(12345) == '12345') and impersonate that actor during claim mapping.","attack_or_counterexample":"Two tokens where one carries sub='12345' (string actor) and the attacker carries sub=12345 (integer), probing for coercion-based identity collision.","evidence":"Trusted current-head diff hunk at docs/implementation-compliance.md:135 observed the dedicated regression 'tests/test_authz.py::test_numeric_sub_claim_cannot_impersonate_string_subject' registered in the buyer-evidence test list, and Coverage execution evidence reports Result PASS for the supported repository test suites at this head, so the integer-subject impersonation attempt is covered by a passing regression; Trusted current-head source binding at docs/implementation-compliance.md:135; source-line-sha256=6bf50c61931f61818561b30453f15a15dba06913d3e48d9687a861188d00d809","outcome":"falsified"},{"path":"pyproject.toml","line":15,"hypothesis":"Adding cryptography==50.0.0 as a direct runtime dependency breaks hash-pinned installs (--require-hashes) or desynchronizes the requirement locks, failing CI/runtime startup.","attack_or_counterexample":"Fresh install path 'python -m pip install --require-hashes -r requirements-dev.txt' followed by the repository test command python3 -m pytest tests, exercising the new pin end to end.","evidence":"Trusted current-head diff at pyproject.toml:15 observed 'cryptography==50.0.0' added to [project].dependencies and the corresponding hash-pinned block added at requirements-dev.txt:129 (plus requirements.txt/requirements-test.txt lock updates per the diff stat); Coverage execution evidence reports Result PASS (supported repository test suites passed) and Failed GitHub Check evidence reports no completed failed checks at head 80966aede42682b993f3f269a543e96f07e92514, so installs and tests under the new pin succeed; Trusted current-head source binding at pyproject.toml:15; source-line-sha256=b601d3dced072a77bc397758a8fa2d0115c62915229ee3e4b390f162a5f6bb8a","outcome":"falsified"}],"residual_risk":"The supplied bounded evidence packet self-reports truncation after 12000 of 93139 bytes, so focused hunks for src/sdp/authz.py, tests/test_api.py, tests/test_authz.py, requirements.txt, requirements-test.in, and requirements-test.txt were not individually inspectable, and direct reads of the head checkout are blocked in this isolated environment. Line-level verification of the authz implementation therefore rests on the trusted Coverage execution evidence (Result PASS), zero active failed GitHub Checks, zero unresolved threads, the CodeGraph up-to-date index over all changed paths, and docs-to-diff test-name cross-references rather than direct reading of every authz.py hunk. mergeStateStatus 'blocked' reflects branch policy/review state, not a merge conflict."}
  • Result: APPROVE
  • Reason: Current-head trusted evidence affirmatively supports the Keyverse claim-alias hardening: fail-closed subject/tenant/role claim validation is documented and regression-tested, the cryptography==50.0.0 pin is consistently propagated across pyproject.toml and hash-pinned requirement locks, Coverage execution evidence reports PASS, and there are no active failed checks or unresolved review threads at head 80966ae.
  • Head SHA: 80966aede42682b993f3f269a543e96f07e92514
  • Workflow run: 32702327909
  • Workflow attempt: 1

@opencode-agent
opencode-agent Bot dismissed their stale review August 24, 2026 15:16

Superseded automated OpenCode change request from a previous head; exact current head 80966ae has a later OpenCode approval.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 5 potential issues.

Open in Devin Review

Comment thread requirements-dev.txt
Comment on lines -129 to -131
colorama==0.4.6 \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Windows hash-pinned install broken by dropped colorama pin

colorama was removed entirely from requirements.txt and requirements-dev.txt, but click (via uvicorn) and pytest still require it on Windows. pip install --require-hashes -r requirements-dev.txt on Windows — the README's documented PowerShell workflow — then pulls colorama transitively with no pinned hash and fails. requirements-test.txt still keeps it win32-marked, confirming the removal is inconsistent.

Prompt for agents
requirements.txt and requirements-dev.txt were regenerated in a way that dropped the colorama pin entirely, while requirements-test.txt still lists `colorama==0.4.6 ; sys_platform == 'win32'`. Because click (a uvicorn dependency) and pytest require colorama on Windows, a `pip install --require-hashes -r requirements-dev.txt` (or requirements.txt) on Windows will fail: pip resolves colorama transitively but has no pinned hash for it in --require-hashes mode. The README documents a Windows PowerShell dev flow (`. .venv/Scripts/Activate.ps1` then `pip install --require-hashes -r requirements-dev.txt`). Regenerate requirements.txt and requirements-dev.txt with the same universal resolution used for requirements-test.txt (per CLAUDE.md, using uv pip compile --generate-hashes) so that the win32-marked colorama pin is retained consistently across all lock files.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/sdp/authz.py
Comment on lines +135 to +149
def _tenant_claim_value(claims: dict[str, Any]) -> str:
"""Validate all tenant aliases before selecting the first configured one."""
tenant_ids: list[str] = []
for key in _TENANT_CLAIMS:
if key not in claims:
continue
tenant_id = _single_string_claim(claims[key], key)
if tenant_id is None:
raise ValueError("missing tenant claim")
tenant_ids.append(tenant_id)
if not tenant_ids:
raise ValueError("missing tenant claim")
if len(set(tenant_ids)) != 1:
raise ValueError("conflicting tenant claims")
return tenant_ids[0]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Multiple tenant aliases now reject on any mismatch

_tenant_claim_value (authz.py) validates every present tenant alias and raises conflicting tenant claims if any differ, replacing the old precedence selection. A token carrying both a tenant id and a differently-valued organization/org display name was previously accepted but is now rejected outright. Worth confirming no target IdP emits distinct tenant aliases.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/sdp/authz.py
Comment on lines +118 to +132
def _subject_claim_value(claims: dict[str, Any]) -> str:
"""Select the first valid subject alias and reject malformed present values.

Subject aliases are a precedence list (preferred_username, email, sub). A
missing or JSON-null alias may fall through, but a present non-string or
blank value is fail-closed so a coerced JSON type cannot become
ActorContext.subject.
"""
for key in _SUBJECT_CLAIMS:
if key not in claims:
continue
subject = _single_string_claim(claims[key], key)
if subject is not None:
return subject
raise ValueError("missing subject claim")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Blank subject alias rejected instead of falling through

_single_string_claim (authz.py) raises on a present blank string, and _subject_claim_value only falls through on missing/JSON-null aliases. A token with preferred_username: "" plus a valid email is now rejected, where the old preferred_username or email or sub chain used the email. Blank aliases are common from some IdPs; this asymmetry with null handling could reject real tokens.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/sdp/authz.py
Comment on lines +31 to +38
_KEYVERSE_ROLE_MAP = {
"member": ["data-analyst"],
"data-analyst": ["data-analyst"],
"data-admin": ["data-admin", "data-analyst"],
"admin": ["admin", "data-analyst"],
"platform-admin": ["platform-admin", "admin", "data-analyst"],
"security": ["security"],
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Keyverse data-admin maps to an inert application role

_KEYVERSE_ROLE_MAP maps data-admin to ["data-admin", "data-analyst"], but src/sdp/policy.py recognizes only admin/platform-admin/data-analyst/security; data-admin appears there only as an obligation string. The data-admin role is therefore inert — non-escalating, but it grants no authority beyond data-analyst.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread requirements-dev.txt
--hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
--hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
# via semantic-data-portal (pyproject.toml)
hypothesis==6.165.3 \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Divergent hypothesis pins across lock files

requirements-dev.txt pins hypothesis==6.165.3 while requirements-test.txt pins hypothesis==6.156.6, both from pyproject's hypothesis>=6.100.0. Dev and CI then run different hypothesis versions, which can make property-test behavior differ between environments.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@opencode-agent
opencode-agent Bot dismissed their stale review August 26, 2026 05:24

Superseded automated OpenCode approval whose explicit review evidence does not match exact current head 0ce6d1f; a fresh current-head review is required.

@opencode-agent
opencode-agent Bot disabled auto-merge August 26, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: medium Normal-priority or P2 work status: needs-review Open pull request requiring current-head review or checks type: bug Defect or incorrect behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants