fix(console): the persona pane no longer locks out the recommended setup - #183
Conversation
`holderGate` disabled every button on the pane unless the caller was an unscoped admin, and explained that this was the only credential that could reach the holder-scoped tasks. That was true when it was written and stopped being true when the agent gained `persona-holder` (verifiable-trust-infrastructure#1286): a context-scoped entry granted that capability reaches them too — and it is now the *recommended* shape, since OpenVTC's setup asks for exactly it. So the console was disabling itself for the configuration the stack now tells people to create, under a message sending them to widen a credential when a capability grant is the better answer. `isUnscopedHolder` is sufficient and no longer necessary, and `auth/whoami` reports roles and scopes but not capabilities, so this console cannot tell which a caller holds. What it must not do is claim to know. It now explains what the task needs, names both ways to satisfy it, says plainly that it cannot see which you have — and stands aside. The agent was always the one deciding; the comment on `isUnscopedHolder` said so already. The caution also speaks the vocabulary now (`design-docs/persona-vocabulary.md`): your *facts*, not "the holder's attribute pool". ## Where it lives Moved to `manager/holder-gate.ts`, with `isUnscopedHolder` alongside it — one decision, one module. The reason is the one the consent view already follows: what the screen says here is a security property, and nothing tests a component's reasoning. The pane's test runner cannot load a `.tsx` file, so a decision left inside one is a decision no test can reach. `use-vta.ts` re-exports the predicate, so importers are unaffected. ## Follow-up Making this certain again means `auth/whoami` returning capabilities — a spec change, then trust-tasks-rs, then the VTA. Worth doing; not worth guessing in the meantime. Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
🛡️ AI Agentic Security Code Review8 AI-confirmed issues. Mandatory to check: 🔒 Security Code Review Report Details🛡️ Security Code Review Report — PR #183
🗺️ Scan CoverageModules scanned: 1 · with findings: 1 · files: 6 · findings: 12
⚖️ Cross-Finding Reconciliation1 root-cause cluster(s) received divergent verdicts across findings that share the same file + weakness. These are surfaced (not auto-resolved) — a reviewer should confirm the verdicts are intentionally different, not an artifact of findings being judged in isolation:
Executive Summary
🔒 Security IssuesConfirmed Vulnerabilities (8)🟡 UI-only advisory replaces enforced access control on destructive persona operations (client-side authorization bypass)
🧠 AI Triage:
📝 Description: The 🌱 Root Cause: Client-side advisory text is treated as a substitute for an actual access-control check in the rendered UI. All 🔎 Evidence: 💥 Impact: Removes a defense-in-depth UI control that previously prevented under-privileged operators from attempting destructive persona operations (delete profile, create binding, add fact/face) they are not authorized to perform, shifting 100% of enforcement onto a downstream service; any latent gap in that service's authorization becomes directly exploitable via a UI that no longer discourages or blocks the attempt. Confidentiality: low · Integrity: medium · Availability: low 🎯 Attack Scenario: A caller with a context-scoped admin credential (not an unscoped holder, and without the 🔧 Remediation:
Priority: Short-term Re-introduce a real client-side capability check (once Secure code:
🟡 Client-side removal of disabled/blocking UI state on destructive persona-pool operations (broken access control relies on client-only advisory gate)
🧠 AI Triage:
📝 Description: The MR removes the 🌱 Root Cause: The identity/authority check ( 🔎 Evidence: 💥 Impact: If the backend does not independently and correctly enforce the holder/capability check on every mutating persona-pool endpoint, removing the client-side disable allows an under-privileged operator to attempt (and potentially complete) destructive operations (delete/add/bind identity facts and faces) that cross trust-context boundaries, corrupting or exposing cross-tenant identity data. Confidentiality: medium · Integrity: high · Availability: low 🎯 Attack Scenario: A context-scoped (non-unscoped) admin who does not actually hold the 🔧 Remediation:
Priority: Short-term Confirm and document that every persona-pool mutation endpoint independently re-validates authority server-side (require_super_admin OR persona-holder capability) regardless of what the console displays. If that guarantee is not airtight, restore a disabling UX state (even if imprecise) as defense-in-depth, or fetch capabilities via an updated Secure code:
🟡 Client-side authorization gate removed from destructive persona actions (fail-open UI, real enforcement missing/unverifiable server-side)
🧠 AI Triage:
📝 Description: The MR removes the 🌱 Root Cause: The refactor changes 🔎 Evidence: 💥 Impact: An under-privileged caller could attempt and potentially execute destructive deletion of cross-context identity/persona data without any client-side warning, increasing risk of unauthorized data loss across trust contexts if server-side enforcement is incomplete or if the console is later relied upon as a control point. Confidentiality: medium · Integrity: high · Availability: medium 🎯 Attack Scenario: A caller without sufficient authority (e.g., a context-scoped Admin lacking 🔧 Remediation:
Priority: Short-term Restore passing the Secure code:
🟡 Client-side-only authorization gate removed from destructive/mutating actions (Broken Access Control)
🧠 AI Triage:
📝 Description: The diff removes the 🌱 Root Cause: The UI previously used 🔎 Evidence: 💥 Impact: If the backend/agent-side capability check referenced in code comments ( Confidentiality: medium · Integrity: medium · Availability: low 🎯 Attack Scenario: A context-scoped administrator (not an unscoped holder) opens the persona/identity console. Under the prior code, the Delete/Add controls for cross-context persona facts were disabled outright when 🔧 Remediation:
Priority: Short-term Re-introduce a UI-side gate (disable + tooltip) as defense-in-depth even though authoritative enforcement should live server-side, OR explicitly confirm and test that every mutating/destructive action (create fact, delete profile) is independently and fully authorized server-side, and add a regression test proving that a context-scoped admin without Secure code:
🟡 Client-side removal of holder-gate disabling is a UI-only control, not a security boundary
🧠 AI Triage:
📝 Description: The persona editor buttons that previously disabled destructive/privileged actions when 🌱 Root Cause: Client-side enablement/disablement of sensitive actions was removed from being enforced (disabled) and reduced to advisory text only, while actual authorization enforcement now solely depends on the backend agent's own checks (which are not shown in this diff). 🔎 Evidence: 🎯 Attack Scenario: If the backend enforcement (agent-side capability/role check) has any gap, a scoped/non-holder caller could still submit persona pool mutation requests since the UI no longer blocks the action locally; the security fully depends on server-side re-validation of every request.
🟡 Authorization decision relies on client-visible roles/scopes that cannot represent capabilities
🧠 AI Triage:
📝 Description: The 🌱 Root Cause: Incomplete or insufficient authorization data model exposed to the client (missing capabilities) forces the client into a permissive fallback pattern. 🔎 Evidence: 🎯 Attack Scenario: A context-scoped caller without the
🟡 Authorization-relevant rationale exposed verbatim to client via holderGate() message revealing internal capability model
🧠 AI Triage:
📝 Description: The advisory message returned to the UI explicitly explains the internal authorization model, including the exact capability name ( 🌱 Root Cause: Detailed internal authorization design decisions and capability names are embedded in a string rendered directly in the UI tooltip/note, without abstraction. 🔎 Evidence: 💥 Impact: Minor information leakage about internal authorization model aids reconnaissance for privilege escalation attempts, though it does not itself grant access. Confidentiality: low · Integrity: none · Availability: none 🎯 Attack Scenario: An attacker with limited privileges views the tooltip and learns the precise capability name ( 🔧 Remediation:
Priority: Short-term Use a generic caution message that does not reveal exact capability identifiers or internal check names to end users; keep detailed rationale in code comments/docs rather than user-facing strings. Secure code:
Generated by Agentic Sec — AI Security Validation Agent Details🛡️ Threat Model & Affect Analysis — PR #183
📋 Affect AnalysisChange SummaryThis PR refactors the client-side 'holderGate' authorization-hint logic out of a .tsx component into a standalone, testable holder-gate.ts module, and simultaneously removes all client-side Diff: +130 / -68 lines 📁 File Classificationspackages/extension/src/manager/holder-gate.ts
packages/extension/src/manager/panes/persona-editors.tsx
packages/extension/src/manager/panes/persona-map.tsx
packages/extension/src/manager/panes/persona-setup.tsx
packages/extension/src/manager/use-vta.ts
packages/extension/tests/manager-holder-gate.test.mts
🛡️ STRIDE Threat ModelIdentified Threats (10)🟠 STRIDE-1: Client-Side Authorization Bypass in DeleteProfile Destructive Control
Description: Delete button in DeleteProfile in persona-editors.tsx allows unauthorized destructive delete attempts due to removal of the disabled/disabledReason UI gate, resulting in reliance on unverified server-side enforcement for authorization Evidence: packages/extension/src/manager/panes/persona-editors.tsx:650-660 Attack Scenario:
🔎 Threat Clue: Derived from persona-editors.tsx, persona-map.tsx via DeleteProfile Delete button, Destructive component in DetailStrip
Preconditions: Attacker holds a context-scoped admin credential or any authority object that fails isUnscopedHolder() but can still reach the UI., Server-side agent enforcement of require_super_admin or persona-holder capability is either not implemented, buggy, or not covering the delete/mutation RPC path exercised by DeleteProfile., No independent test suite validates server-side enforcement for this exact destructive path. Existing Controls: holderGate() still renders an advisory tooltip (title=denied) informing the user of the authority requirement. • ConsentCeremony/confirm phase requires an additional explicit confirmation click before the destructive action executes. • PR documentation states enforcement is intended to occur server-side in the agent on every task. Recommended Mitigations: Independently verify and test that the backend/agent enforces require_super_admin or persona-holder capability on every delete/mutation RPC invoked by DeleteProfile and Destructive components. • Add integration tests that attempt the delete RPC with a context-scoped admin token and assert server-side rejection. • Consider re-adding a soft client-side warning modal (not a hard block) that requires explicit acknowledgment before submission when holderGate() returns non-null. • Extend auth/whoami (or an equivalent endpoint) to report capabilities so the client can make a more accurate advisory decision without becoming the sole enforcement point. 🟡 STRIDE-2: Reliance on Client-Removed disabledReason Prop Leaving Stale Callers Exposed
Description: DeleteProfile component signature change in persona-editors.tsx allows type-safety loss for external callers due to removal of the disabledReason prop without a corresponding runtime compatibility shim, resulting in silent behavior regressions in any out-of-scope caller Evidence: packages/extension/src/manager/panes/persona-editors.tsx:598-610 Attack Scenario:
🔎 Threat Clue: Derived from persona-editors.tsx, unknown external callers via DeleteProfile component API
Preconditions: A caller of DeleteProfile exists outside the six files included in this PR's diff., That caller was not updated in this PR and continues to pass disabledReason expecting enforcement. Existing Controls: TypeScript type-checking would likely flag the removed prop as an unknown/excess property at build time in strict mode. • PR review flag explicitly calls out this exact risk for downstream verification. Recommended Mitigations: Run a full-repository search for all usages of DeleteProfile and Destructive components before merging. • Add a lint/CI check that fails the build if any component prop referencing security-relevant gating (disabledReason, denied) is passed to a component that no longer consumes it. • Add a deprecation-safe wrapper that logs a warning if disabledReason is still passed but ignored. 🟡 STRIDE-3: Insufficient Authority Model Enabling Unscoped Admin Misclassification
Description: isUnscopedHolder heuristic in holder-gate.ts allows misclassification of privilege boundaries due to reliance on the combination of role=admin AND scopes.length===0 without verifying persona-holder capability, resulting in incorrect advisory decisions surfaced to end users that could be misapplied elsewhere as a true authorization check Evidence: packages/extension/src/manager/holder-gate.ts:27-30 Attack Scenario:
🔎 Threat Clue: Derived from holder-gate.ts, use-vta.ts via isUnscopedHolder, holderGate
Preconditions: auth/whoami response integrity is not cryptographically verified client-side or is susceptible to tampering., A future or existing code path uses isUnscopedHolder as a hard security boundary rather than advisory-only, contrary to its documented intent. Existing Controls: Extensive documentation within holder-gate.ts explicitly warns that this is advisory only and the agent decides again on every task. • Unit tests in manager-holder-gate.test.mts validate the exact boolean logic of isUnscopedHolder for several role/scope combinations. Recommended Mitigations: Enforce TLS and integrity verification on auth/whoami responses to prevent tampering with roles/scopes data. • Add explicit code comments and lint rules preventing isUnscopedHolder from being used outside advisory/UI-hint contexts. • Extend auth/whoami to report capabilities directly, removing the need for heuristic inference entirely, as already planned per the module's own documentation. 🔵 STRIDE-4: Advisory Tooltip Information Disclosure of Authorization Model Internals
Description: title tooltip in holderGate() in holder-gate.ts allows disclosure of internal authorization architecture details due to verbose advisory text describing exact capability names and enforcement mechanisms, resulting in reconnaissance information useful for crafting privilege escalation attempts against the backend agent Evidence: packages/extension/src/manager/holder-gate.ts:58-64 Attack Scenario:
🔎 Threat Clue: Derived from holder-gate.ts via title tooltip on gated buttons
Preconditions: Attacker has any authenticated session capable of reaching the persona manager UI., Attacker performs reconnaissance via browser dev tools or accessibility tooling to read tooltip title attributes. Existing Controls: Message is only shown to already-authenticated users within the extension's manager UI, not exposed pre-auth. • No secrets, tokens, or credentials are included in the tooltip text. Recommended Mitigations: Reduce tooltip verbosity to avoid naming exact internal capability strings (e.g., 'persona-holder') and enforcement architecture details. • Log/monitor repeated triggering of holderGate() advisory paths as a potential reconnaissance signal. • Rely on generic messaging such as 'You may not have sufficient authority for this action; the system will verify.' 🟡 STRIDE-5: Race Condition Between Delete Preview and Confirm in Destructive Component
Description: preview() async callback in Destructive component in persona-map.tsx allows a TOCTOU condition due to the profile authorization state being 're-asked' at preview time but the actual delete RPC being submitted separately after confirmation, resulting in stale-authority-based destructive actions if the caller's authority changes between preview and execution Evidence: packages/extension/src/manager/panes/persona-map.tsx:825-833 Attack Scenario:
🔎 Threat Clue: Derived from persona-map.tsx via Destructive preview/confirm flow
Preconditions: A revocation or authority downgrade occurs between preview() and the final confirm click., Server-side agent does not perform a fresh, atomic authorization check immediately before executing the delete mutation. Existing Controls: preview() is explicitly re-fetched rather than cached from the map view, reducing staleness window. • ConsentCeremony requires an explicit additional confirmation step, adding latency that could either help or reduce the exploitable window depending on server-side checks. Recommended Mitigations: Ensure server-side agent performs authorization checks atomically at execution time, not merely at request receipt. • Add a short-lived, single-use authorization token issued at preview time and validated at confirm time to detect staleness. • Log and alert on any delete executed with an authority state that differs from the state at preview time. 🟠 STRIDE-6: Missing Independent Verification of Server-Side require_super_admin Enforcement
Description: Persona pool RPC endpoints referenced by holderGate documentation allows unauthorized cross-context data access due to the client-side PR assuming server-side require_super_admin/persona-holder enforcement exists without providing verification artifacts in this changeset, resulting in a potential single-point-of-failure authorization gap Evidence: packages/extension/src/manager/holder-gate.ts:44-56 Attack Scenario:
🔎 Threat Clue: Derived from Backend agent (out of scope for this diff) via persona/* RPC surface (not in diff)
Preconditions: Server-side agent enforcement code (not present in this changeset) must be independently verified., This PR is merged and deployed before server-side enforcement is confirmed complete and tested. Existing Controls: PR description and code comments explicitly assert that server-side enforcement exists ('the agent decides again on every task regardless of this UI state'). • Recon review flags explicitly call out the need to verify this independently. Recommended Mitigations: Block merge of this PR until server-side test coverage for require_super_admin/persona-holder enforcement on every persona/* RPC (create, read, update, delete, bind) is confirmed and linked. • Require a cross-repository CI gate or contract test verifying agent-side authorization for the same RPC surface exercised by this UI. • Add a security regression test that attempts each persona/* mutation with a context-scoped admin token against a real or mocked agent and asserts rejection. 🔵 STRIDE-7: Test Suite Repudiation Gap for Removed UI Gating Behavior
Description: manager-holder-gate.test.mts allows insufficient traceability of destructive UI actions due to only testing the string-returning holderGate function in isolation without integration tests asserting the Delete button remains enabled and no audit log is verified, resulting in a repudiation gap where removed client-side gating changes cannot be regression-tested against actual UI component behavior Evidence: packages/extension/tests/manager-holder-gate.test.mts:1-51 Attack Scenario:
🔎 Threat Clue: Derived from manager-holder-gate.test.mts via Test suite coverage
Preconditions: No component-level (React Testing Library or equivalent) tests exist for the five affected components in this PR., An incident investigation needs to reconstruct historical UI gating behavior. Existing Controls: Pure-function unit tests exist for the underlying decision logic (holderGate/isUnscopedHolder), which partially documents intended behavior. Recommended Mitigations: Add component-level tests (e.g., using a DOM testing library compatible with the project's test runner) asserting the presence/absence of disabled attributes on the five affected buttons under various authority states. • Add server-side audit logging for all delete/mutation actions capturing the caller's authority snapshot at execution time for forensic traceability. • Document in CI which UI gating behaviors are and are not covered by automated tests. 🔵 STRIDE-8: Type-Only Import Boundary Circumvention Risk in holder-gate.ts
Description: type-only import of Authority in holder-gate.ts allows a maintainability/security drift risk due to the module boundary being enforced only by convention and comments rather than tooling, resulting in potential future accidental introduction of runtime component dependencies that could break the standalone testability guarantee the module claims Evidence: packages/extension/src/manager/holder-gate.ts:9-11 Attack Scenario:
🔎 Threat Clue: Derived from holder-gate.ts via N/A
Preconditions: A future code change violates the type-only import convention without any automated enforcement., No CI lint rule (e.g., @typescript-eslint/consistent-type-imports enforced as error) exists to catch this. Existing Controls: Extensive code comments document the type-only import requirement and its rationale. • TypeScript's Recommended Mitigations: Add an ESLint rule (e.g., import/no-cycle or consistent-type-imports) enforced in CI to guarantee holder-gate.ts never accumulates runtime dependencies on component code. • Add a CI smoke test that imports holder-gate.ts in isolation (as the test file does) to catch accidental runtime coupling early. ⚪ STRIDE-9: Prompt-Injection-Style Instruction Embedded in Source Comments Targeting Automated Review Tools
Description: Verbose narrative code comments in holder-gate.ts allow potential manipulation of automated security review/LLM-based tooling due to persuasive natural-language justifications embedded directly in source ('advisory, not a gate', 'the agent decides again'), resulting in a risk that automated reviewers may under-scrutinize the actual authorization gap if they defer to the embedded rationale rather than independently verifying server-side enforcement Evidence: packages/extension/src/manager/holder-gate.ts:34-56 Attack Scenario:
🔎 Threat Clue: Derived from holder-gate.ts, manager-holder-gate.test.mts via N/A - code review process
Preconditions: A human or automated reviewer relies solely on the embedded comments' framing rather than independently verifying claims about server-side enforcement., No linked server-side test evidence is provided alongside the narrative justification. Existing Controls: This threat model instance itself explicitly treats the narrative comments as data-under-analysis and calls out the unverified server-side enforcement claim as STRIDE-6, demonstrating correct handling. • The PR does include a genuine, testable unit test suite for the underlying pure function, showing some verifiable rigor beyond prose. Recommended Mitigations: Require security-critical PRs to include or link server-side test evidence rather than relying on in-code narrative justification alone. • Train/configure automated review tooling to always independently verify claims made in code comments about security properties, rather than accepting them at face value. • Add a PR template checklist item requiring explicit linkage to server-side enforcement tests when client-side gates are removed. 🔵 STRIDE-10: Denied State Computation Timing Enables UI Race with Async whoami Fetch
Description: holderGate(null) returning null in holder-gate.ts allows a brief window of fully-enabled destructive controls due to the null-authority (loading state) case being treated identically to the authorized case, resulting in users being able to click Delete/Save buttons before auth/whoami resolves and the true authority is known Evidence: packages/extension/src/manager/holder-gate.ts:58-59 Attack Scenario:
🔎 Threat Clue: Derived from holder-gate.ts, persona-editors.tsx via Delete/Save buttons during authority loading
Preconditions: auth/whoami fetch has meaningful latency (network delay)., User or automated tooling interacts with destructive controls within the loading window., Server does not independently re-validate authority at the time of mutation execution. Existing Controls: busy state disables Save buttons during in-flight save operations for AttributeEditor/ProfileEditor (though not tied to authority loading specifically). • ConsentCeremony confirm step adds a small delay/friction that may allow whoami to resolve in most real-world timing scenarios. Recommended Mitigations: Explicitly disable all destructive/mutating buttons while authority is still loading (authority === null AND not yet resolved), distinct from the 'no restriction' null-return semantics of holderGate. • Ensure server-side enforcement is authoritative regardless of client timing, closing this race at the true trust boundary. • Add a loading skeleton/spinner state that structurally prevents interaction with action buttons until authority resolves. 🍝 PASTA Threat ModelApplication PurposeThe VTA browser extension provides a persona/identity management console allowing operators to view, edit, bind, and delete identity attributes (facts, faces, bindings) across trust contexts, delegating final authorization decisions to a backend verifiable-trust agent. Inherent Risks
ObjectivesRisk: Accept residual risk of misleading advisory-only UI in exchange for correctness once server-side enforcement is fully verified. Business Impact Analysis (1)BIA-1: Persona Pool Identity Data Management (Critical)End-to-end process by which operators view, create, edit, bind, and delete cross-context persona identity data (facts, faces, bindings) through the browser extension console. MTD: 01 days 00:00 hours | RTO: 00 days 04:00 hours | RPO: 00 days 01:00 hours
Technical ScopeRoles (2): RO-1 Unscoped Admin / Persona Holder · RO-2 Context-Scoped Admin Actors (2): AC-1 Persona Manager UI (Client) · AC-2 Backend Trust Agent Entry Points (4): EP-1 Delete Button (DeleteProfile) · EP-2 Destructive Delete Control (DetailStrip) · EP-3 Save Button (AttributeEditor/ProfileEditor) · EP-4 AddTile / Bind Buttons (IdentityMap) Threat Actors (2): TA-1 Malicious Context-Scoped Insider · TA-2 Compromised Session Attacker Infrastructure (1): IF-1 Browser Extension Runtime Trust Boundaries (2): TB-1 Browser Extension Client Boundary · TB-2 Backend Agent Trust Boundary External Entities (2): EE-1 Context-Scoped Admin User · EE-2 Unscoped Holder Agent Credential System Components (5): SC-1 Persona Manager UI (persona-editors.tsx, persona-map.tsx, persona-setup.tsx) · SC-2 Holder Gate Module (holder-gate.ts) · SC-3 use-vta.ts Authority Hook · SC-4 Backend Verifiable Trust Agent (out of scope) · SC-5 Persona Pool Data Store Resources And Assets (2): RA-1 Persona Pool Identity Profiles · RA-2 Authority Object (roles/scopes/session) Technologies And Dependencies (2): TD-1 React · TD-2 node:test Use Cases (2)
⚔️ Attack Scenarios (3)SC-1: Persona Manager UI---
config:
layout: dagre
look: classic
theme: dark
---
flowchart LR
subgraph SL1["1. System Component"]
direction LR
SC1@{ shape: rect, label: "SC-1: Persona Manager UI" }
end
subgraph SL2["2. Weaknesses"]
direction LR
CWE1@{ shape: rect, label: "CWE-602: Client-Side Enforcement of Server-Side Security" }
CWE2@{ shape: rect, label: "CWE-862: Missing Authorization" }
end
subgraph SL3["3. Attack Patterns"]
direction LR
CAPEC1@{ shape: rect, label: "CAPEC-122: Privilege Abuse" }
end
subgraph SL4["4. Threats"]
direction LR
ST1@{ shape: rect, label: "STRIDE-1: Client-Side Authorization Bypass in DeleteProfile<br><i>High / Likely</i>" }
ST6@{ shape: rect, label: "STRIDE-6: Missing Independent Verification of require_super_admin<br><i>High / Possible</i>" }
end
subgraph SL5["5. Threat Actors"]
direction LR
TA1@{ shape: rect, label: "TA-1: Malicious Context-Scoped Insider<br><i>Access identity data beyond authorized scope</i>" }
end
SC1 --> CWE1
CWE1 --> CAPEC1
CAPEC1 --> ST1
ST1 --> TA1
SC1 --> CWE2
CWE2 --> ST6
ST6 --> TA1
linkStyle 0 stroke:#FF0000,stroke-width:2px
linkStyle 1 stroke:#FF0000,stroke-width:2px
linkStyle 2 stroke:#FF0000,stroke-width:2px
linkStyle 3 stroke:#FF0000,stroke-width:2px
linkStyle 4 stroke:#FF0000,stroke-width:2px
linkStyle 5 stroke:#FF0000,stroke-width:2px
linkStyle 6 stroke:#FF0000,stroke-width:2px
SC-2: Holder Gate Module---
config:
layout: dagre
look: classic
theme: dark
---
flowchart LR
subgraph SL1["1. System Component"]
direction LR
SC2@{ shape: rect, label: "SC-2: Holder Gate Module" }
end
subgraph SL2["2. Weaknesses"]
direction LR
CWE3@{ shape: rect, label: "CWE-863: Incorrect Authorization" }
CWE4@{ shape: rect, label: "CWE-200: Exposure of Sensitive Information" }
end
subgraph SL3["3. Attack Patterns"]
direction LR
CAPEC2@{ shape: rect, label: "CAPEC-115: Authentication Bypass" }
CAPEC3@{ shape: rect, label: "CAPEC-118: Information Gathering" }
end
subgraph SL4["4. Threats"]
direction LR
ST3@{ shape: rect, label: "STRIDE-3: Insufficient Authority Model<br><i>Medium / Possible</i>" }
ST4@{ shape: rect, label: "STRIDE-4: Advisory Tooltip Information Disclosure<br><i>Low / Possible</i>" }
end
subgraph SL5["5. Threat Actors"]
direction LR
TA2@{ shape: rect, label: "TA-2: Compromised Session Attacker<br><i>Leverage stolen session before revocation</i>" }
end
SC2 --> CWE3
CWE3 --> CAPEC2
CAPEC2 --> ST3
ST3 --> TA2
SC2 --> CWE4
CWE4 --> CAPEC3
CAPEC3 --> ST4
ST4 --> TA2
linkStyle 0 stroke:#FFA500,stroke-width:2px
linkStyle 1 stroke:#FFA500,stroke-width:2px
linkStyle 2 stroke:#FFA500,stroke-width:2px
linkStyle 3 stroke:#FFA500,stroke-width:2px
linkStyle 4 stroke:#00FF00,stroke-width:2px
linkStyle 5 stroke:#00FF00,stroke-width:2px
linkStyle 6 stroke:#00FF00,stroke-width:2px
linkStyle 7 stroke:#00FF00,stroke-width:2px
SC-4: Backend Verifiable Trust Agent---
config:
layout: dagre
look: classic
theme: dark
---
flowchart LR
subgraph SL1["1. System Component"]
direction LR
SC4@{ shape: rect, label: "SC-4: Backend Verifiable Trust Agent" }
end
subgraph SL2["2. Weaknesses"]
direction LR
CWE5@{ shape: rect, label: "CWE-306: Missing Authentication for Critical Function" }
CWE6@{ shape: rect, label: "CWE-367: TOCTOU Race Condition" }
end
subgraph SL3["3. Attack Patterns"]
direction LR
CAPEC4@{ shape: rect, label: "CAPEC-122: Privilege Abuse" }
CAPEC5@{ shape: rect, label: "CAPEC-25: Forced Deadlock/Race Window Exploit" }
end
subgraph SL4["4. Threats"]
direction LR
ST6b@{ shape: rect, label: "STRIDE-6: Missing Verification of require_super_admin<br><i>High / Possible</i>" }
ST5@{ shape: rect, label: "STRIDE-5: Race Condition in Destructive Preview/Confirm<br><i>Medium / Possible</i>" }
end
subgraph SL5["5. Threat Actors"]
direction LR
TA1b@{ shape: rect, label: "TA-1: Malicious Context-Scoped Insider<br><i>Access identity data beyond authorized scope</i>" }
TA2b@{ shape: rect, label: "TA-2: Compromised Session Attacker<br><i>Leverage stolen session before revocation</i>" }
end
SC4 --> CWE5
CWE5 --> CAPEC4
CAPEC4 --> ST6b
ST6b --> TA1b
SC4 --> CWE6
CWE6 --> CAPEC5
CAPEC5 --> ST5
ST5 --> TA2b
linkStyle 0 stroke:#FF0000,stroke-width:2px
linkStyle 1 stroke:#FF0000,stroke-width:2px
linkStyle 2 stroke:#FF0000,stroke-width:2px
linkStyle 3 stroke:#FF0000,stroke-width:2px
linkStyle 4 stroke:#FFA500,stroke-width:2px
linkStyle 5 stroke:#FFA500,stroke-width:2px
linkStyle 6 stroke:#FFA500,stroke-width:2px
linkStyle 7 stroke:#FFA500,stroke-width:2px
📊 Risk SummaryTotal Threats: 10 By Severity: Low: 4 · High: 2 · Medium: 3 · Informational: 1 By Category: Tampering: 5 · Elevation of Privilege: 3 · Denial of Service: 1 · Information Disclosure: 3 · Repudiation: 3 · Spoofing: 1 🎯 Attack SurfaceKill Chain 1: An attacker holding a context-scoped admin credential (roles=["admin"], scopes=["work"]) — previously blocked entirely by the removed disabled/disabledReason bindings in AttributeEditor, ProfileEditor, DeleteProfile, IdentityMap, and DetailStrip — now encounters a fully clickable UI across all five components (STRIDE-1, STRIDE-3). The attacker proceeds through the Delete confirmation ceremony and submits a delete/mutation RPC directly to the backend agent (SC-4), which is the sole remaining enforcement point but whose require_super_admin/persona-holder capability check is unverified within this changeset (STRIDE-6). If that server-side check has any gap — missing coverage on the delete path specifically, a logic bug, or simple absence — the attacker achieves unauthorized cross-context deletion of persona pool identity data (RA-1), a critical and irreversible impact. 🛡️ Risk Mitigation StrategyPriority 1 (Immediate): The single highest-priority gap is the unverified assumption that the backend agent enforces require_super_admin/persona-holder capability checks on every persona/* mutation RPC (STRIDE-6, STRIDE-1). Before merging or deploying this PR, obtain and link server-side test evidence — ideally an integration test that attempts each mutation (create, update, bind, delete) against the real or mocked agent using a context-scoped admin token and asserts rejection. This single control closes the majority of residual risk introduced by this PR, since the client-side change is only safe if this server-side boundary is airtight. Generated by Agentic Sec — Threat Model & Affect Analysis Agent 📊 Summary & findings
Confirmed (8)
|
Found while sweeping the persona vocabulary across the three repos. This one is
not cosmetic — the console currently disables itself for the configuration the
stack now recommends.
The stale claim
holderGatedisabled every button on the persona pane unless the caller was anunscoped admin, and said so:
True when written. Not true since
Capability::PersonaHolder(verifiable-trust-infrastructure#1286): a context-scoped entry granted that
capability reaches the holder-scoped tasks too — and it is now the recommended
shape, because OpenVTC's setup asks for exactly it (
--admin-holder).So an operator following the documented path saw a dead pane and a message
telling them to widen their agent credential, when a capability grant is the
better answer and the one they already had.
What it does now
isUnscopedHolderis sufficient and no longer necessary.auth/whoamireports roles and scopes but not capabilities, so this console cannot tell which
a caller holds — and the thing it must not do is claim to know.
The caution now names both ways to satisfy the agent, says plainly that the
console cannot see which you have, and stands aside rather than disabling.
The agent decides on every task regardless —
isUnscopedHolder's own commentsaid as much before this change.
It also speaks
design-docs/persona-vocabulary.md: your facts, not "theholder's attribute pool".
Where it lives
Moved to
manager/holder-gate.tswithisUnscopedHolderbeside it — onedecision, one module — following the precedent the consent view already sets:
what the screen says here is a security property, and nothing tests a
component's reasoning. Concretely, the pane's test runner cannot load a
.tsxfile, so a decision left inside one is a decision no test can reach.
use-vta.tsre-exports the predicate; importers are unaffected.
manager-holder-gate.test.mtspins all four cases, including that the cautionnames
persona-holderand the unscoped credential — naming only one is whatmade the old message wrong — and that it carries no word the vocabulary keeps
off the screen.
Follow-up
Making this certain rather than cautious means
auth/whoamireturningcapabilities: a spec change, then trust-tasks-rs, then the VTA. Worth doing, and
not worth guessing in the meantime.
npm run lint(tsc -b) clean;npm test851 passing across the threeworkspaces.