MEDIUM-tier OAuth: verified-owner elevation + claim-by-name (VPS / sos_mcp_sse) - #189
Open
servathadi wants to merge 2 commits into
Open
MEDIUM-tier OAuth: verified-owner elevation + claim-by-name (VPS / sos_mcp_sse)#189servathadi wants to merge 2 commits into
servathadi wants to merge 2 commits into
Conversation
…side)
§2.3 _elevate_verified_owner_if_eligible: after _require_auth, re-resolve a
worker_oauth customer via inkwell-api resolve-owner (server-side, by sub) and
elevate IN-PLACE to scope="tenant"/role=highest when sub_matched + owner/editor
membership. Unknown/unverified/low-role/error → customer wall UNCHANGED
(fail-closed). Wired into both /mcp Streamable HTTP and /sse transports.
list_projects/sign_in prefer cached owner_memberships (OAuth /connections/lookup
misses — token is internal-hash). Gated behind SOS_SECURITY_MODE (default medium).
§2.4 _handle_claim ("claim" tool): RLS-FIRST via _peer_tenant_meta + HMAC
tenant-slug compare, qNFT read ONLY after RLS passes (closes BLOCK-3 — qNFT
presence is never an authz input). Novel name that passes RLS but has no qNFT →
qnft_required River-gate; physically NO mint call. Owner/editor tenant scope
only; customer/tenant-agent forbidden (mirrors as_agent).
12 unit tests (elevation gate + claim ordering, all 4 closures re-verified).
REPORT-ONLY: built in an isolated worktree; the live /home/mumega/sos-public-
kernel file is UNTOUCHED, no service restart.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ingle source)
_handle_claim previously admitted {owner,admin,editor} but delegates the bind to
_handle_as_agent, whose tenant-scope gate is owner-ONLY — an editor/admin session
would pass claim then be bounced by as_agent (fail-closed but drift-prone). Make
claim's tenant role gate identical (owner-only) so the two predicates can't drift
open independently; widening tenant-claim authority is now a single-point change
in _handle_as_agent. Scope elevation (§2.3) still grants tenant scope to
owner/admin/editor for general tool access — only the claim/as_agent primitive is
owner-gated (intentional layer separation).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SENSITIVE AUTH — REPORT-ONLY. Built in an ISOLATED worktree; the live
/home/mumega/sos-public-kernel file is UNTOUCHED, no service restart. Gate before flip.
VPS-Python half of the MEDIUM-tier identity model (spec §2.3–2.4). Pairs with
Mumega-com/mumega-com PR #222 (worker + inkwell-api half).
What this does
_elevate_verified_owner_if_eligible— after_require_auth, re-resolvea
worker_oauthcustomer via inkwell-api resolve-owner (server-side, BY SUB) andelevate IN-PLACE to
scope="tenant"/role=highestwhensub_matched+ owner/editor membership. Unknown/unverified/low-role/error → customer wall UNCHANGED
(fail-closed). Wired into both /mcp (Streamable HTTP) and /sse transports.
list_projects/sign_inprefer cachedowner_memberships(OAuth/connections/lookup misses — token is internal-hash). Gated behind
SOS_SECURITY_MODE(defaultmedium;lowdisables elevation entirely)._handle_claim(claimtool) — RLS-FIRST via_peer_tenant_meta+ HMACtenant-slug compare; qNFT read ONLY after RLS passes (closes BLOCK-3 — qNFT
presence is never an authz input). Novel name that passes RLS but has no qNFT →
qnft_requiredRiver-gate; physically NO mint call. Owner-only (single-sourcedwith as_agent, WARN-B); customer/tenant-agent forbidden.
Tests
12 unit tests (elevation gate + claim ordering) re-verifying all 4 closures. All pass.
Adversarial pass (kasra-review, parallel gate)
BLOCK-2/3/4 + novel-name-no-mint GREEN. WARN-C = flip gate: nginx :6070 X-
scrub + loopback bind must land + forged-header test (e) pass before live elevation.*
🤖 Generated with Claude Code