feat: update vta authentication to include client identity - #36
Conversation
Signed-off-by: Robert Kwolek <robert.k@affinidi.com> Signed-by-DID: did:webvh:QmNYECKwYUJExB19ucYGwRjvRGyPPk5ShGLUkxjiLLyyVm:affinidi.github.io:did-docs:robert#key-0
🛡️ AI Agentic Security Code Review1 finding needs a human to review/validate. Mandatory to check: 🔒 Security Code Review Report Details🛡️ Security Code Review Report — PR #36
🗺️ Scan CoverageModules scanned: 1 · with findings: 1 · files: 1 · findings: 5
Executive Summary
🔒 Security Issues
|
| Field | Detail |
|---|---|
| Severity | LOW |
| Location | crates/did-git-sign/src/vta.rs:35 |
| Finding ID | github_pr-f3afc52405ff |
| CWE | CWE-345, CWE-613 |
| OWASP | A08:2021 - Software and Data Integrity Failures |
| MITRE ATT&CK | T1550.001 - Use Alternate Authentication Material: Application Access Token |
| CAPEC | CAPEC-39, CAPEC-593 |
| DREAD | 3.8 |
| Reachability | 🔴 Reachable |
| Exploit Maturity | conceptual |
| Detection Source | skill_scan |
Summary: The authenticate() function in vta.rs attaches a cached token to the VtaClient via set_token() without first validating the token's expiry or signature locally, relying entirely on the remote VTA mediator to reject invalid tokens.
📝 Description:
A stale or tampered token could be forwarded to the VTA mediator on every signing attempt until the mediator itself rejects it, wasting a network round-trip and potentially succeeding if the mediator's own validation has gaps.
🧪 Proof of Concept:
No local decoding/validation of the token (expiry, signature, audience) occurs before it is attached to the client, pushing all trust decisions to the remote mediator with no client-side fail-fast check.
if creds.mediator_did.is_none()
&& let Some(token) = config::load_cached_token(&cfg.did_key_id)
{
let identity = ClientIdentity::did_key(
&creds.credential_did,
&creds.private_key_multibase,
&creds.vta_did,
);
let client = VtaClient::new(&creds.vta_url).with_identity(identity);
client.set_token(token);
return Ok((client, creds));
}
Vulnerable lines: 27, 38
🔁 Reproduction Steps:
- Obtain local write access to the token cache file/store used by config::load_cached_token.
- Overwrite the cached entry for a target did_key_id with an expired or malformed token value.
- Invoke authenticate(cfg) with creds.mediator_did == None so the cached-token branch runs (vta.rs:27-38).
- Observe that client.set_token(token) is called unconditionally at line 35 with no local expiry/signature check.
- Attempt a signing operation and observe whether the VTA mediator rejects the token (expected) or, in a misconfigured mediator, accepts it.
🔎 Evidence: crates/did-git-sign/src/vta.rs:35
client.set_token(token);
💥 Impact:
A stale or tampered token could be forwarded to the VTA mediator on every signing attempt until the mediator itself rejects it, wasting a network round-trip and potentially succeeding if the mediator's own validation has gaps.
Confidentiality: Low · Integrity: Low - a stale/forged token could be attempted against the mediator · Availability: None
🧭 Reachability:
- Network exposure: internal
- Auth barrier: basic
- Attack path: EP-003 client.set_token(token) ← config::load_cached_token(&cfg.did_key_id) at vta.rs:29,35; requires local cache tampering to exploit
⚖️ Triage Factors:
| Factor | Value |
|---|---|
| Fixable | ✅ Yes |
| Exploitability | low |
| Business impact | low |
| Public exploit | None known |
| Environment | unknown |
Attack scenario: An attacker with local write access to the token cache could substitute a stale or forged token, which is attached to the client without local validation and forwarded to the VTA mediator.
🔧 Remediation:
⚠️ AI-generated fix. Review, test in staging, and validate against your architecture before applying.
Adding local expiry/signature validation before attaching the token prevents stale or tampered tokens from being sent to the mediator, fails fast, and forces re-authentication.
Vulnerable code:
client.set_token(token);
Secure code:
let claims = validate_token_locally(&token)
.context("cached token failed local validation (expired/invalid signature)")?;
if claims.is_expired() {
config::invalidate_cached_token(&cfg.did_key_id);
bail!("cached token expired; re-authentication required");
}
client.set_token(token);
Additional recommendations:
- Integrity-protect the on-disk token cache (e.g. HMAC-sign entries with a key derived from the local keyring) so tampering can be detected.
- Restrict filesystem permissions on the token cache directory to the owning user only.
- Rely on strict server-side validation at the VTA mediator as a defense-in-depth backstop.
🔍 Validation Log
- Verdict:
⚠️ Must-Review-By-Human- Confidence: 90%
- AI Validation Evidence: EVIDENCE FOUND: The line 'client.set_token(token);' does genuinely exist in the real source at the cached-token fast path: 'if creds.mediator_did.is_none() && let Some(token) = config::load_cached_token(&cfg.did_key_id) { let client = VtaClient::new(&creds.vta_url); client.set_token(token); return Ok((client, creds)); }'. No local validation of the token's expiry, signature, or audience is visible before set_token is called; config::cache_token stores 'access_expires_at' but load_cached_token's
- 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 #36
| Field | Value |
|---|---|
| Repository | OpenVTC/verifiable-git-infrastructure |
| Branch | feat/signed-by-did-trailer → main |
| Generated | 2026-09-05 |
ℹ️ 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 adds client identity binding to the VTA (Verifiable Trust Anchor) authentication cached-token fast path. Previously, when reusing a cached token (no mediator DID present), a VtaClient was constructed with no bound ClientIdentity. This change constructs a ClientIdentity from DID key material (credential_did, private_key_multibase, vta_did) and attaches it via with_identity() before setting the cached token, closing an identity-less authentication gap in this branch.
Diff: +7 / -1 lines
Types: security, feature
Risk Assessment
- Overall Risk: medium
- Review Priority: before_merge
- Pentest Needed: true
- Security Review Needed: true
This is a small (7 additions/1 deletion), single-file, security-critical change to an authentication code path in a project whose entire purpose is verifiable/trustworthy git signing. The change itself is directionally positive — it fixes a real gap where cached-token reuse previously produced an identity-less client. However, the fix is incomplete: it does not establish or verify that the token being reused was actually issued for the identity now being constructed, leaving a residual authentication-confusion risk (CWE-346/CWE-287) that could allow misattributed signing if the VTA mediator's server-side checks are not strict. Additionally, private key material flows through a new call path with unconfirmed zeroization guarantees, and the change deepens reliance on an internal SDK (vta_sdk) whose provenance cannot be assessed from this diff alone. Given the criticality of this code path (it underpins the trust root for all signed git commits per prior analysis BIA-1), manual security review before merge is warranted, focused on token-identity binding validation and confirmation of SDK-level secret hygiene.
Review Focus Areas:
- Full source of vta_sdk::client::ClientIdentity::did_key, VtaClient::with_identity, and VtaClient::set_token to confirm cryptographic correctness and secret hygiene (not available in this diff).
- Full source of crate::config, specifically load_cached_token and how/where the token-to-identity binding is (or is not) verified at cache-write and cache-read time.
- Confirm whether the mediator-based authentication path (not shown, used when mediator_did.is_some()) performs stronger validation than this cached-token path, and whether that asymmetry constitutes an exploitable downgrade path.
- Cargo.toml/Cargo.lock for vta_sdk version pinning and dependency integrity verification.
Pentest Focus:
- Attempt to reuse a cached token issued for one credential_did/vta_did pairing against a ClientIdentity constructed from a different pairing, and observe whether the VTA mediator rejects the mismatch server-side.
- Test the VTA mediator's server-side validation of the identity/token binding to confirm it independently enforces subject consistency (defense-in-depth for the client-side gap this diff addresses).
- Attempt to supply a malformed/corrupted private_key_multibase value to determine whether ClientIdentity::did_key panics (DoS) or returns a graceful error.
- Inspect process memory (e.g. via core dump or debugger) after invoking the cached-token authentication path to determine whether private key material remains resident longer than necessary.
⚠️ Security Implications
🟡 Closes prior identity-less client construction gap in cached-token authentication path
Closes prior identity-less client construction gap in cached-token authentication path
Action: Merge this change as a net-positive security improvement, but pair it with the MUST-priority action below to close the remaining token-to-identity binding validation gap.
🟠 No validation that reused cached token matches the newly constructed ClientIdentity subject
No validation that reused cached token matches the newly constructed ClientIdentity subject
Action: Before calling set_token(), verify the token's embedded subject/DID claim matches creds.credential_did and creds.vta_did. Reject and force re-authentication on mismatch. Alternatively, store an identity fingerprint alongside the cached token at write time and compare on load.
🟡 Private key material (private_key_multibase) now flows into a new SDK call path (ClientIdentity::did_key) with no visible zeroization or format validation
Private key material (private_key_multibase) now flows into a new SDK call path (ClientIdentity::did_key) with no visible zeroization or format validation
Action: Audit vta_sdk::client::ClientIdentity for ZeroizeOnDrop coverage on private key buffers. Add local multibase format/length validation before this call. Consider wrapping secret values in a secrecy::Secret-style type end-to-end rather than passing raw &String references.
🟡 New dependency on vta_sdk::ClientIdentity expands trust surface in an internal/custom SDK
New dependency on vta_sdk::ClientIdentity expands trust surface in an internal/custom SDK
Action: Pin vta_sdk to an exact, checksummed/signed version or commit. Track it via SBOM/provenance attestation and require security review for changes to its authentication primitives.
🧩 Affected Components
| Component | Impact | Change | What Changed |
|---|---|---|---|
| did-git-sign::vta::authenticate() | high | modified | The cached-token authentication branch (taken when creds.mediator_did.is_none() and a cached token exists) now constructs a ClientIdentity f |
📁 File Classifications
crates/did-git-sign/src/vta.rs
- Type: security
💡 Recommendations
- MUST — Add validation that the cached token's subject/issuer claim matches the newly constructed ClientIdentity's credential_did/vta_did before calling set_token; reject mismatches. (effort: medium)
- Prevents authentication confusion between a reused token and a freshly constructed identity, which is the most significant residual risk this diff leaves unaddressed.
- MUST — Confirm and, if necessary, implement ZeroizeOnDrop for private key material handled by ClientIdentity in vta_sdk. (effort: medium)
- private_key_multibase is now passed into a new SDK call path; without confirmed zeroization, this critical secret risks memory disclosure.
- SHOULD — Add local format/length validation for private_key_multibase, credential_did, and vta_did before constructing ClientIdentity. (effort: small)
- Prevents SDK-level panics (DoS) and reduces risk of binding to malformed or tampered identifiers.
- SHOULD — Pin the vta_sdk dependency to a specific verified version/commit with checksum validation. (effort: medium)
- This diff deepens reliance on vta_sdk's authentication primitives (ClientIdentity); an unpinned or unverified dependency is a supply-chain risk to the entire signing trust chain.
- CONSIDER — Add structured audit logging for the cached-token authentication branch. (effort: small)
- Improves forensic traceability of which identity/token pair was used for a given signing session.
- CONSIDER — Add a regression test asserting the cached-token branch always returns an identity-bound VtaClient. (effort: small)
- Prevents this exact gap from silently reappearing in future refactors of authenticate().
✅ Positive Observations
- The core intent of this PR — attaching a ClientIdentity to the VtaClient in the cached-token branch — closes a genuine prior gap where the client was constructed with no identity bound to it at all.
- Use of DID-key based decentralized identity aligns with the project's stated purpose (verifiable git infrastructure) and is a more principled authentication mechanism than bare bearer tokens alone.
- The change is minimal and targeted (single function, single branch), reducing the blast radius of the code change itself and making review tractable.
- Existing use of anyhow-based structured error handling and the zeroize dependency in the module indicate an overall security-conscious codebase design, even where individual gaps remain.
🛡️ STRIDE Threat Model
Identified Threats (11)
🟠 STRIDE-1: Cached Token Reuse Without Identity Binding Validation in authenticate
| Field | Detail |
|---|---|
| Category | Spoofing, Tampering, Elevation of Privilege |
| Severity | High |
| Likelihood | Likely |
| CVSS | 7.7 CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Medium |
| CWE | CWE-287,CWE-346 |
| CAPEC | CAPEC-593,CAPEC-21 |
| OWASP | A07:2021 - Identification and Authentication Failures |
Description: authenticate() in vta.rs allows cached-token session hijacking due to attaching a new ClientIdentity to a previously-issued token without re-validating that the token was originally issued for that same identity/DID, resulting in potential authentication confusion between credential identity and token bearer.
Evidence: crates/did-git-sign/src/vta.rs:27-38
if creds.mediator_did.is_none() && let Some(token) = config::load_cached_token(&cfg.did_key_id) {
let identity = ClientIdentity::did_key(&creds.credential_did, &creds.private_key_multibase, &creds.vta_did);
let client = VtaClient::new(&creds.vta_url).with_identity(identity);
client.set_t
Attack Scenario:
- Attacker gains write access to the local token cache location referenced by config::load_cached_token(&cfg.did_key_id), e.g. via a shared build agent, CI runner cache poisoning, or a prior compromise of the local filesystem.
- Attacker plants or reuses a cached token associated with cfg.did_key_id that was originally issued to a different credential_did / vta_did pairing.
- authenticate() executes the branch when creds.mediator_did.is_none() and a cached token exists, constructing a new ClientIdentity::did_key(&creds.credential_did, &creds.private_key_multibase, &creds.vta_did) independently from the token that is about to be attached.
- client.set_token(token) binds the stale/foreign token to the freshly constructed identity without the SDK or calling code verifying that the token's original subject matches creds.credential_did/vta_did.
- The function returns Ok((client, creds)) and the caller proceeds to sign/authenticate git operations as if the identity and token are consistently bound, potentially allowing actions to be attributed to, or authorized under, the wrong DID.
- Downstream signing operations executed via the resulting VtaClient may succeed against the VTA mediator using a mismatched identity/token pair if the VTA server does not strictly validate token-subject binding server-side.
🔎 Threat Clue: Derived from COMP-001, COMP-002 via EP-001, EP-002, EP-003
- Data Flows: cached_token -> ClientIdentity binding -> VtaClient
Preconditions: Local or CI-level write/read access to the cached token store keyed by did_key_id., The VTA server-side token validation does not perform strict binding checks between the token subject and the presented ClientIdentity on every request., creds.mediator_did is None, forcing the token-cache code path.
Existing Controls: Token caching is scoped by cfg.did_key_id. • zeroize crate is imported, suggesting some secret material is scrubbed from memory after use.
Recommended Mitigations: Validate that the cached token's embedded subject/DID claim matches creds.credential_did and creds.vta_did before calling set_token. • Invalidate and refuse cached tokens when the identity used to construct ClientIdentity differs from the identity recorded at cache-write time. • Store the identity fingerprint alongside the cached token and compare on load in config::load_cached_token. • Enforce server-side strict token-to-identity binding validation in the VTA mediator.
🟠 STRIDE-2: Private Key Multibase Exposure via ClientIdentity Construction in authenticate
| Field | Detail |
|---|---|
| Category | Information Disclosure |
| Severity | High |
| Likelihood | Possible |
| CVSS | 7.1 CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Medium |
| CWE | CWE-316,CWE-226 |
| CAPEC | CAPEC-37,CAPEC-150 |
| OWASP | A02:2021 - Cryptographic Failures |
Description: ClientIdentity::did_key(&creds.credential_did, &creds.private_key_multibase, &creds.vta_did) in vta.rs allows sensitive private key material disclosure due to passing private_key_multibase by reference into SDK code without visible zeroization guarantees on the ClientIdentity object itself, resulting in potential private key leakage via memory dumps, core dumps, or swap.
Evidence: crates/did-git-sign/src/vta.rs:30-34
let identity = ClientIdentity::did_key(
&creds.credential_did,
&creds.private_key_multibase,
&creds.vta_did,
);
Attack Scenario:
- creds.private_key_multibase is loaded from SigningConfig/VtaCredentials (source not shown, likely from disk or environment).
- authenticate() passes &creds.private_key_multibase directly into ClientIdentity::did_key() at vta.rs line 30-34.
- The zeroize crate is imported at the top of the file for VtaCredentials or related structs, but the diff does not show ClientIdentity itself implementing Zeroize or ZeroizeOnDrop.
- If the process crashes, is core-dumped, swapped to disk, or inspected via a debugger/memory-scraping tool (e.g. an attacker with local code execution or a malicious CI plugin), the private key multibase string may remain resident in memory inside the ClientIdentity struct or intermediate stack frames.
- Attacker with local memory access (e.g. compromised CI runner, shared host, or malicious dependency) extracts the private key and impersonates the DID key holder against the VTA mediator.
🔎 Threat Clue: Derived from COMP-002 via EP-002
- Data Flows: creds.private_key_multibase -> ClientIdentity::did_key -> VtaClient
Preconditions: Attacker has local memory-read capability on the host/CI runner (e.g. via another compromised process, core dump collection, or swap file access)., ClientIdentity or the underlying vta_sdk types do not zeroize the private key bytes on drop.
Existing Controls: zeroize crate is imported in vta.rs, indicating awareness of secret hygiene needs elsewhere in the codebase.
Recommended Mitigations: Ensure ClientIdentity and any intermediate buffers holding private_key_multibase implement ZeroizeOnDrop. • Avoid passing raw private key strings by reference across API boundaries; use secret-wrapping types (e.g. secrecy::Secret) end-to-end. • Disable core dumps for the signing process and mark memory pages containing key material as non-swappable (mlock) where the OS supports it. • Audit vta_sdk::client::ClientIdentity source for secret handling guarantees.
🟡 STRIDE-3: Unauthenticated Token Attachment via set_token Prior to Server Validation in vta.rs
| Field | Detail |
|---|---|
| Category | Spoofing, Tampering |
| Severity | Medium |
| Likelihood | Possible |
| CVSS | 5.3 CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Low |
| CWE | CWE-345,CWE-613 |
| CAPEC | CAPEC-39,CAPEC-593 |
| OWASP | A07:2021 - Identification and Authentication Failures |
Description: client.set_token(token) in vta.rs allows local token injection due to the function accepting and attaching any cached token value without local validation of expiry, signature, or audience claims prior to use, resulting in potential use of stale, forged, or replayed tokens against the VTA mediator.
Evidence: crates/did-git-sign/src/vta.rs:35
client.set_token(token);
Attack Scenario:
- config::load_cached_token(&cfg.did_key_id) retrieves a token from local cache (file, keyring, or similar) without shown validation logic in this excerpt.
- Attacker with local write access to the cache path replaces the cached token with an expired, forged, or replayed token value.
- authenticate() unconditionally calls client.set_token(token) at line 35 without decoding/validating the JWT-like structure, expiry, or signature locally before attaching it to the client.
- The malformed or stale token is sent to the VTA mediator on the next SDK call; if the mediator's server-side validation is lenient or has clock-skew tolerance bugs, the attacker-supplied token may be accepted.
- Successful authentication with a stale/forged token permits unauthorized signing operations under the associated did_key_id.
🔎 Threat Clue: Derived from COMP-002 via EP-003
- Data Flows: cached_token -> client.set_token
Preconditions: Attacker can write to or tamper with the local token cache file/store., VTA mediator has weak or missing server-side token validation (expiry, signature, audience).
Existing Controls: Token caching keyed by did_key_id limits blast radius to a single key identity. • Server-side VTA validation (not visible in this excerpt) may still reject invalid tokens.
Recommended Mitigations: Validate token expiry and signature locally immediately after config::load_cached_token before calling set_token. • Encrypt and integrity-protect the local token cache (e.g. HMAC-sign cache entries) so tampering is detectable. • Restrict filesystem permissions on the token cache directory to the owning user/process only.
🟡 STRIDE-4: Missing Repudiation Controls for Cached-Token Authentication Path in authenticate
| Field | Detail |
|---|---|
| Category | Repudiation |
| Severity | Medium |
| Likelihood | Possible |
| CVSS | 4.8 CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Low |
| CWE | CWE-778 |
| CAPEC | CAPEC-93 |
| OWASP | A09:2021 - Security Logging and Monitoring Failures |
Description: authenticate() in vta.rs allows repudiation of signing actions performed via the cached-token fast path due to absence of any visible audit logging when a cached token is reused with a newly constructed identity, resulting in insufficient forensic traceability of who authenticated and when.
Evidence: crates/did-git-sign/src/vta.rs:27-38
if creds.mediator_did.is_none() && let Some(token) = config::load_cached_token(&cfg.did_key_id) {
...
return Ok((client, creds));
}
Attack Scenario:
- A user or automated CI process triggers authenticate() and hits the cached-token branch (creds.mediator_did.is_none() and a cached token exists).
- The function silently constructs a ClientIdentity, attaches the token, and returns without emitting any log entry, event, or audit record referencing the did_key_id, token identifier, or timestamp.
- Later, if a malicious or unauthorized git signing action is discovered, investigators cannot correlate the signing action to a specific authentication event because no audit trail exists for the cached-token reuse path.
- Attacker who successfully abuses STRIDE-1 or STRIDE-3 gains additional cover because their actions leave no distinguishing log trace from legitimate cached-token use.
🔎 Threat Clue: Derived from COMP-001 via EP-001
- Data Flows: authenticate() return path
Preconditions: No external logging/monitoring wraps calls to authenticate() at the caller layer., Investigation relies on in-process logs that this function does not emit.
Existing Controls: Function returns a Result type enabling upstream error propagation, which may incidentally surface failures (but not successes) to callers.
Recommended Mitigations: Emit structured audit log entries (with did_key_id, vta_did, token fingerprint, timestamp) whenever the cached-token fast path is used. • Include a monotonic request/session identifier in logs to support forensic correlation across the signing pipeline. • Forward audit events to a tamper-evident, centrally aggregated log store.
🔵 STRIDE-5: Resource Exhaustion via Unbounded ClientIdentity Construction Loop in authenticate Callers
| Field | Detail |
|---|---|
| Category | Denial of Service |
| Severity | Low |
| Likelihood | Unlikely |
| CVSS | 3.1 CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N |
| Residual Severity | Low |
| CWE | CWE-400 |
| CAPEC | CAPEC-125 |
| OWASP | A04:2021 - Insecure Design |
Description: authenticate() in vta.rs allows minor resource exhaustion due to lack of visible rate limiting or backoff when repeatedly invoked with a valid but rapidly-expiring cached token, resulting in potential excessive VtaClient/identity object churn and mediator load.
Evidence: crates/did-git-sign/src/vta.rs:27-38
if creds.mediator_did.is_none() && let Some(token) = config::load_cached_token(&cfg.did_key_id) { ... }
Attack Scenario:
- A calling process (e.g. a CI job or malicious automation) repeatedly invokes authenticate() in a tight loop.
- Each call reconstructs a ClientIdentity and VtaClient object and attaches a token, with no visible caching of the constructed client or rate limiting shown in this excerpt.
- If the cached token is expired on every call, the SDK may re-trigger network calls to the VTA mediator for validation or refresh, amplifying load.
- Repeated invocation from many parallel CI jobs or a compromised automation script could produce a denial-of-service effect on the VTA mediator endpoint referenced by creds.vta_url.
🔎 Threat Clue: Derived from COMP-001, COMP-002 via EP-001, EP-002
- Data Flows: authenticate() invocation loop
Preconditions: Caller code invokes authenticate() at high frequency without backoff., No client-side rate limiting exists in the visible code path.
Existing Controls: AutoConnect trait/type imported, which may implement some connection reuse/backoff logic not visible in this excerpt.
Recommended Mitigations: Implement client-side rate limiting or exponential backoff around authenticate() calls. • Cache constructed VtaClient/ClientIdentity objects across calls within a process lifetime when safe to do so. • Add server-side rate limiting on the VTA mediator authentication endpoint.
🟡 STRIDE-6: DID Key Tampering via Unvalidated credential_did/vta_did Fields in ClientIdentity Construction
| Field | Detail |
|---|---|
| Category | Tampering, Spoofing |
| Severity | Medium |
| Likelihood | Possible |
| CVSS | 5.9 CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Medium |
| CWE | CWE-345,CWE-20 |
| CAPEC | CAPEC-176,CAPEC-21 |
| OWASP | A08:2021 - Software and Data Integrity Failures |
Description: ClientIdentity::did_key construction in vta.rs allows identity tampering due to no visible local validation of creds.credential_did and creds.vta_did format/ownership before binding them into the identity object, resulting in potential impersonation of a different DID subject or mediator target.
Evidence: crates/did-git-sign/src/vta.rs:30-34
let identity = ClientIdentity::did_key(
&creds.credential_did,
&creds.private_key_multibase,
&creds.vta_did,
);
Attack Scenario:
- creds (a VtaCredentials struct) is loaded via config::load_cached_token-adjacent config loading logic (not fully shown), populating credential_did, private_key_multibase, and vta_did from a config file or environment.
- An attacker with local write access to the config source (e.g. a CI variable injection, compromised config file, or environment variable override) tampers with creds.credential_did or creds.vta_did.
- authenticate() passes these attacker-controlled fields directly into ClientIdentity::did_key(&creds.credential_did, &creds.private_key_multibase, &creds.vta_did) at line 30-34 without validating that the DID strings are well-formed or match an expected allowlist.
- The resulting ClientIdentity is bound to a mediator DID or credential DID that differs from the intended one, potentially causing the signing operation to be attributed to, or trusted by, the wrong verifiable trust anchor.
- Downstream git commit signatures produced under this identity may be accepted by relying parties as valid, undermining the trust chain the VTA infrastructure is designed to provide.
🔎 Threat Clue: Derived from COMP-002 via EP-002
- Data Flows: creds.credential_did / creds.vta_did -> ClientIdentity
Preconditions: Attacker can modify the configuration source feeding SigningConfig/VtaCredentials (env vars, config file, CI secret injection)., No schema/format validation or allowlist check exists on credential_did/vta_did before use.
Existing Controls: Credentials are typed via SigningConfig/VtaCredentials structs, providing some structural validation at deserialization time.
Recommended Mitigations: Validate credential_did and vta_did against an expected DID method/format and, where possible, an allowlist of known trust anchors before constructing ClientIdentity. • Sign or checksum configuration files containing DID/credential material to detect tampering. • Restrict permissions on configuration sources to prevent unauthorized modification.
🟡 STRIDE-7: Supply Chain Risk from Unpinned/Trusted vta_sdk Dependency Providing ClientIdentity and Authentication Primitives
| Field | Detail |
|---|---|
| Category | Tampering, Elevation of Privilege |
| Severity | Medium |
| Likelihood | Possible |
| CVSS | 6.0 CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Medium |
| CWE | CWE-1104,CWE-494 |
| CAPEC | CAPEC-437 |
| OWASP | A08:2021 - Software and Data Integrity Failures |
Description: The vta_sdk dependency import in vta.rs allows supply-chain compromise of authentication primitives due to the addition of new API surface (ClientIdentity) sourced from a custom/internal SDK without visible pinning, checksum verification, or provenance attestation in this excerpt, resulting in potential injection of malicious authentication logic if the dependency is compromised.
Evidence: crates/did-git-sign/src/vta.rs:1-3
use vta_sdk::client::{AutoConnect, ClientIdentity, ConnectedVta, VtaClient};
Attack Scenario:
- The project depends on the internal/custom vta_sdk crate, importing AutoConnect, ClientIdentity, ConnectedVta, and VtaClient as shown in the diff's use statement.
- If the vta_sdk crate is fetched from a mutable source (e.g. a git dependency without a pinned commit hash, or a private registry without strict integrity checks), an attacker who compromises the SDK's build/publish pipeline could inject malicious behavior into ClientIdentity::did_key or VtaClient::set_token.
- Because did-git-sign directly trusts and calls these SDK functions with sensitive material (private_key_multibase, tokens), a compromised SDK version could exfiltrate private keys or forge authentication tokens transparently to the calling code.
- The application would continue to function normally from the caller's perspective, masking the compromise (classic supply-chain backdoor pattern).
- Downstream, forged or exfiltrated credentials could be used to sign malicious git commits that appear to originate from a legitimate, verifiably-trusted identity, undermining the entire verifiable-git-infrastructure trust model this project implements.
🔎 Threat Clue: Derived from COMP-002 via EP-002, EP-003
- Data Flows: vta_sdk import -> ClientIdentity/VtaClient usage
Preconditions: vta_sdk dependency is not pinned to an immutable, verified commit/version with checksum or signature verification., Build pipeline lacks software bill of materials (SBOM) tracking or dependency provenance attestation for internal crates.
Existing Controls: Use of a dedicated internal SDK (vta_sdk) rather than ad-hoc crypto code suggests centralized security ownership of authentication primitives.
Recommended Mitigations: Pin vta_sdk to an exact version or commit hash with cryptographic verification (e.g. Cargo.lock plus checksum/signature validation). • Adopt reproducible builds and SBOM generation for the did-git-sign crate and its dependency tree. • Require code review and signed commits for any changes to vta_sdk's authentication primitives (ClientIdentity, VtaClient). • Implement dependency update monitoring/alerting for the internal SDK repository.
🔵 STRIDE-8: TOCTOU Race Between Cached Token Load and Identity Binding in authenticate
| Field | Detail |
|---|---|
| Category | Tampering |
| Severity | Low |
| Likelihood | Unlikely |
| CVSS | 3.8 CVSS:4.0/AV:L/AC:H/AT:P/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Low |
| CWE | CWE-367 |
| CAPEC | CAPEC-26 |
| OWASP | A04:2021 - Insecure Design |
Description: config::load_cached_token(&cfg.did_key_id) followed by ClientIdentity construction in vta.rs allows a time-of-check-to-time-of-use race due to the token being read from disk/cache before the identity binding occurs, resulting in potential use of a token that was concurrently invalidated, rotated, or revoked between load and use.
Evidence: crates/did-git-sign/src/vta.rs:28-35
&& let Some(token) = config::load_cached_token(&cfg.did_key_id)
{
let identity = ClientIdentity::did_key(...);
let client = VtaClient::new(&creds.vta_url).with_identity(identity);
client.set_token(token);
return Ok((client, creds));
}
Attack Scenario:
- authenticate() calls config::load_cached_token(&cfg.did_key_id) which reads token state from a file or local store at time T1.
- Concurrently, a token-rotation process or the legitimate user revokes/rotates the cached token at time T2 (T1 < T2 < T3).
- authenticate() proceeds to construct ClientIdentity and call client.set_token(token) at time T3 using the now-stale token value read at T1.
- If the VTA mediator has any grace-period tolerance for recently-rotated tokens, the stale token may still be accepted, allowing a narrow window where revoked credentials remain usable.
- An attacker who can trigger concurrent authenticate() calls (e.g. via parallel CI jobs sharing the same cache) could exploit this window to continue using a token that was meant to be invalidated.
🔎 Threat Clue: Derived from COMP-002 via EP-003
- Data Flows: config::load_cached_token -> client.set_token
Preconditions: Multiple concurrent processes/threads access the same cached token store for the same did_key_id., VTA mediator honors a grace period or lacks strict immediate revocation enforcement.
Existing Controls: Token caching is scoped per did_key_id, limiting cross-identity race conditions.
Recommended Mitigations: Use file locking or atomic read-and-invalidate operations when loading cached tokens. • Design token revocation to be immediately enforced server-side regardless of client-side cache staleness. • Add a short validity check (e.g. re-verify token freshness) immediately before client.set_token is called.
🟡 STRIDE-9: Missing Mediator DID Verification Bypass via mediator_did.is_none() Branch Condition
| Field | Detail |
|---|---|
| Category | Elevation of Privilege, Tampering |
| Severity | Medium |
| Likelihood | Possible |
| CVSS | 5.5 CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N |
| Residual Severity | Low |
| CWE | CWE-807,CWE-696 |
| CAPEC | CAPEC-627 |
| OWASP | A04:2021 - Insecure Design |
Description: The branch condition creds.mediator_did.is_none() in authenticate() allows an authorization bypass of mediator-based verification flows due to the code path skipping any mediator-related trust establishment when mediator_did is absent, resulting in a weaker authentication flow being used whenever an attacker can force this field to be unset.
Evidence: crates/did-git-sign/src/vta.rs:27-28
if creds.mediator_did.is_none()
&& let Some(token) = config::load_cached_token(&cfg.did_key_id)
{
Attack Scenario:
- The authenticate() function branches into the cached-token fast path only when creds.mediator_did.is_none() is true, per line 27-28.
- If an attacker can influence how VtaCredentials is populated (e.g. via config injection, environment variable manipulation, or a race during config reload), they can force mediator_did to be None even in contexts where a mediator-verified flow was intended.
- This forces authenticate() into the simpler, less-verified cached-token + directly-constructed-identity path rather than a (presumably more rigorous) mediator-based authentication path used elsewhere in the file (not shown in this excerpt).
- The attacker benefits from whichever weaker security guarantees apply to the cached-token path (e.g. STRIDE-1, STRIDE-3, STRIDE-6) that would not apply under the mediator-verified path.
- This effectively allows a downgrade attack against the intended authentication strength selection logic.
🔎 Threat Clue: Derived from COMP-001 via EP-001
- Data Flows: creds.mediator_did -> branch selection
Preconditions: Attacker can influence the value of creds.mediator_did (e.g. via config file tampering or environment control)., The mediator-based path (not shown) provides materially stronger security guarantees than the cached-token path.
Existing Controls: Branch condition requires both mediator_did.is_none() AND a valid cached token to exist, narrowing the exploitable window.
Recommended Mitigations: Ensure mediator_did selection is derived from a trusted source and cannot be nulled by untrusted input. • Apply consistent minimum security controls (identity/token binding validation) across both the mediator and non-mediator authentication paths. • Add integrity protection to the configuration fields that determine which authentication branch is selected.
🔵 STRIDE-10: Insufficient Error Context Disclosure via anyhow Context Propagation in vta.rs
| Field | Detail |
|---|---|
| Category | Information Disclosure |
| Severity | Low |
| Likelihood | Unlikely |
| CVSS | 2.6 CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N |
| Residual Severity | Low |
| CWE | CWE-209 |
| CAPEC | CAPEC-215 |
| OWASP | A09:2021 - Security Logging and Monitoring Failures |
Description: anyhow::Context usage throughout vta.rs allows minor information disclosure due to potentially verbose error messages propagating internal state (e.g. DID values, file paths) to logs or CLI output without redaction, resulting in leakage of sensitive configuration details to local logs or terminals.
Evidence: crates/did-git-sign/src/vta.rs:1
use anyhow::{Context, Result, bail};
Attack Scenario:
- authenticate() and related helper functions use anyhow::Context (imported at line 1) to attach descriptive context to errors, a common Rust pattern.
- If any .context(...) or .with_context(...) call (not fully visible in this excerpt) includes interpolated values such as creds.vta_did, creds.credential_did, or file paths from config::load_cached_token, these values propagate into the final error message.
- When authenticate() returns an Err, the calling CLI tool likely prints the anyhow error chain to stderr or a log file for debugging purposes.
- An operator running the tool in a shared terminal session, or a CI log viewer with broad access, could observe DID identifiers or file paths that were not intended to be broadly visible, providing reconnaissance value for a subsequent targeted attack (e.g. combined with STRIDE-6).
🔎 Threat Clue: Derived from COMP-001 via EP-001
- Data Flows: Result<(VtaClient, VtaCredentials)> error propagation
Preconditions: Error context strings embed sensitive identifiers (DIDs, paths, partial credentials)., Error output is written to a log sink accessible to users beyond the intended operator (e.g. shared CI logs).
Existing Controls: anyhow contexts typically wrap human-readable descriptions rather than raw secrets, reducing likelihood of raw key material exposure specifically.
Recommended Mitigations: Audit all .context()/.with_context() call sites in vta.rs and config.rs for inclusion of sensitive identifiers. • Redact or truncate DID values and file paths in user-facing error messages; log full detail only to a restricted-access debug log. • Mark CI log output containing authentication error traces as sensitive/masked in the CI system configuration.
🟡 STRIDE-11: Lack of Input Validation on private_key_multibase Encoding Prior to ClientIdentity Use
| Field | Detail |
|---|---|
| Category | Denial of Service, Tampering |
| Severity | Medium |
| Likelihood | Possible |
| CVSS | 4.6 CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N |
| Residual Severity | Low |
| CWE | CWE-20,CWE-248 |
| CAPEC | CAPEC-153 |
| OWASP | A03:2021 - Injection |
Description: ClientIdentity::did_key(...) in vta.rs allows malformed-input processing due to no visible local validation of creds.private_key_multibase's multibase encoding or length prior to being passed into the SDK, resulting in potential panics, undefined behavior, or crash-based denial of service if the SDK's parser is not fully hardened against malformed multibase strings.
Evidence: crates/did-git-sign/src/vta.rs:30-34
let identity = ClientIdentity::did_key(
&creds.credential_did,
&creds.private_key_multibase,
&creds.vta_did,
);
Attack Scenario:
- creds.private_key_multibase originates from a configuration file or cache that could be corrupted, truncated, or maliciously edited by an attacker with local write access.
- authenticate() passes &creds.private_key_multibase directly to ClientIdentity::did_key without any visible format validation (e.g. multibase prefix check, length check, base58/base64 decode validation) in this excerpt.
- If vta_sdk's internal multibase decoder panics on malformed input (common in early-stage Rust SDKs using unwrap() internally) rather than returning a Result, the authenticate() call could cause the entire did-git-sign process to crash.
- Repeated crashes during automated git signing in a CI pipeline would constitute a denial-of-service condition against the signing workflow, blocking legitimate commits from being signed.
🔎 Threat Clue: Derived from COMP-002 via EP-002
- Data Flows: creds.private_key_multibase -> ClientIdentity::did_key
Preconditions: vta_sdk::client::ClientIdentity::did_key internally uses panics (unwrap/expect) rather than Result-based error handling for malformed multibase input., Attacker or corruption event can alter the stored private_key_multibase value.
Existing Controls: Overall function signature returns Result<...>, suggesting some error paths are handled gracefully elsewhere, though panics bypass Result entirely.
Recommended Mitigations: Validate the multibase prefix and decoded length of private_key_multibase locally before calling ClientIdentity::did_key. • Wrap the SDK call in a panic::catch_unwind boundary if the SDK cannot be modified, converting panics into recoverable errors. • Request/verify that vta_sdk's did_key constructor returns a Result instead of panicking on malformed input.
🍝 PASTA Threat Model
Application Purpose
did-git-sign provides cryptographic git commit signing backed by a Verifiable Trust Anchor (VTA) network using DID-based identities, enabling verifiable provenance of git infrastructure changes for supply-chain security.
Inherent Risks
- The application handles raw private key material (private_key_multibase) in process memory during every signing operation.
- Authentication decisions (mediator-based vs cached-token) are branched on config-controlled fields that may be tamperable.
- The system depends on an internal, less-scrutinized vta_sdk crate for critical authentication primitives.
Objectives
Risk: Treat any compromise of signing credentials as a critical supply-chain risk to downstream consumers of signed commits.; Treat token/identity binding failures as high-priority authentication risks.
Business: Provide cryptographically verifiable provenance for git commits across the OpenVTC ecosystem.; Enable trusted third parties to verify commit authorship via DID-based credentials.
Security: Ensure tokens are only usable by the identity they were issued to.; Protect private key material from disclosure at rest and in memory.; Ensure DID identifiers used for signing cannot be tampered with by unauthorized parties.
Financial: Minimize incident response and remediation costs from credential compromise.; Avoid reputational and contractual damages from a supply-chain trust failure.
Compliance: Align with software supply-chain integrity expectations (e.g. SLSA-style provenance).; Support auditability requirements for signing key usage.
Functional: Authenticate to the VTA mediator using either a cached token or a mediator-negotiated flow.; Bind a ClientIdentity constructed from DID key material to the VtaClient used for signing.
Operational: Ensure signing operations succeed reliably within CI/CD pipelines without excessive latency.; Support token caching to reduce redundant authentication round-trips.
Business Impact Analysis (2)
BIA-1: Verifiable Git Commit Signing (Critical)
The authenticate() function establishes an authenticated VtaClient session used to cryptographically sign git commits under a DID-based identity, forming the trust root for all downstream commit verification.
MTD: 01 days 00:00 hours | RTO: 00 days 04:00 hours | RPO: 00 days 00:15 hours
- Stakeholders: CI/CD Pipeline Operators / Downstream Commit Verifiers / OpenVTC Platform Maintainers / Repository Maintainers
- Dependencies: Local Token Cache Store / VTA Mediator Service / vta_sdk Crate / VtaCredentials Configuration Source
- Disruptions: Cached token cache corruption or unauthorized tampering / Compromise of the vta_sdk supply chain / VTA mediator service outage / Private key material disclosure
- Impacts: Loss of trust in signed commits across the OpenVTC ecosystem / Inability to sign commits, blocking CI/CD release pipelines / Regulatory/compliance failure for supply-chain integrity attestations / Reputational damage if forged commits are discovered
BIA-2: VTA Identity and Token Lifecycle Management (High)
Manages the loading, caching, and binding of DID-based identities and authentication tokens used to authorize signing operations against the VTA mediator.
MTD: 03 days 00:00 hours | RTO: 01 days 00:00 hours | RPO: 00 days 01:00 hours
- Stakeholders: OpenVTC Platform Maintainers / Security Engineering Team / CI/CD Pipeline Operators
- Dependencies: Local Token Cache Store / config Module / zeroize Crate
- Disruptions: Stale or revoked token reuse / Race condition between token load and identity binding / Unvalidated DID field tampering
- Impacts: Unauthorized signing under a mismatched identity / Delayed detection of credential misuse due to lack of audit logging / Increased incident response time during forensic investigation
Technical Scope
Roles (3): RO-1 CI/CD Automation Identity · RO-2 Repository Maintainer · RO-3 VTA Mediator Operator
Actors (3): AC-1 CI Pipeline Process · AC-2 Human Developer/Maintainer · AC-3 VTA Mediator Service
Entry Points (3): EP-1 authenticate Function Call · EP-2 VtaClient Identity Binding · EP-3 Token Attachment Call
Threat Actors (3): TA-1 Malicious CI Insider · TA-2 Supply Chain Attacker · TA-3 Local Host Compromise Actor
Infrastructure (2): IF-1 CI/CD Runner Environment · IF-2 VTA Mediator Backend
Trust Boundaries (3): TB-1 Local CI/Developer Host Boundary · TB-2 VTA Mediator Network Boundary · TB-3 Supply Chain / Build Boundary
External Entities (2): EE-1 VTA Mediator · EE-2 vta_sdk Crate Registry/Source
System Components (5): SC-1 authenticate() Function · SC-2 VtaClient / ClientIdentity (vta_sdk) · SC-3 Local Token Cache Store · SC-4 VtaCredentials Configuration Source · SC-5 VTA Mediator Service
Resources And Assets (3): RA-1 Private Key Multibase Material · RA-2 Cached Authentication Token · RA-3 DID Identifiers (credential_did, vta_did, mediator_did)
Technologies And Dependencies (3): TD-1 vta_sdk · TD-2 anyhow · TD-3 zeroize
Use Cases (1)
- Cached-Token VTA Authentication: A CI pipeline or developer invokes authenticate() which detects a previously cached token for the configured did_key_id, constructs a DID-based ClientIdentity, binds it to a new VtaClient, attaches th
📋 Risk Registry (5)
| ID | Title | Severity | Residual | Priority | Effort |
|---|---|---|---|---|---|
| RISK-001 | Signing sessions may be established with mismatched token and identity bindings, enabling authentication confusion attacks. | High | Medium | Immediate | Medium |
| RISK-002 | Private key material (private_key_multibase) may be exposed via process memory, core dumps, or inadequate zeroization. | High | Medium | Short-Term | Medium |
| RISK-003 | The internal vta_sdk dependency represents a concentrated supply-chain trust point for all authentication primitives. | Medium | Medium | Medium-Term | High |
| RISK-004 | Configuration fields controlling authentication path selection (mediator_did, credential_did, vta_did) lack integrity protection. | Medium | Medium | Short-Term | Medium |
| RISK-005 | Lack of audit logging on the cached-token authentication fast path reduces forensic traceability. | Medium | Low | Medium-Term | Low |
⚔️ Attack Scenarios (3)
SC-1: authenticate() Function
---
config:
layout: dagre
look: classic
theme: dark
---
flowchart LR
subgraph SL1["1. System Component"]
direction LR
SC1@{ shape: rect, label: "SC-1: authenticate() Function" }
end
subgraph SL2["2. Weaknesses"]
direction LR
CWE287@{ shape: rect, label: "CWE-287: Improper Authentication" }
CWE346@{ shape: rect, label: "CWE-346: Origin Validation Error" }
CWE778@{ shape: rect, label: "CWE-778: Insufficient Logging" }
end
subgraph SL3["3. Attack Patterns"]
direction LR
CAPEC593@{ shape: rect, label: "CAPEC-593: Session Hijacking" }
CAPEC21@{ shape: rect, label: "CAPEC-21: Exploitation of Trusted Identifiers" }
CAPEC93@{ shape: rect, label: "CAPEC-93: Log Injection-Tampering-Forging" }
end
subgraph SL4["4. Threats"]
direction LR
STRIDE1@{ shape: rect, label: "STRIDE-1: Cached Token Reuse Without Identity Binding Validation<br><i>High / Likely</i>" }
STRIDE4@{ shape: rect, label: "STRIDE-4: Missing Repudiation Controls for Cached-Token Path<br><i>Medium / Possible</i>" }
end
subgraph SL5["5. Threat Actors"]
direction LR
TA1@{ shape: rect, label: "TA-1: Malicious CI Insider<br><i>Exfiltrate signing credentials or forge commit signatures</i>" }
TA3@{ shape: rect, label: "TA-3: Local Host Compromise Actor<br><i>Extract private key material or cached tokens</i>" }
end
SC1 --> CWE287
SC1 --> CWE346
SC1 --> CWE778
CWE287 --> CAPEC593
CWE346 --> CAPEC21
CWE778 --> CAPEC93
CAPEC593 --> STRIDE1
CAPEC21 --> STRIDE1
CAPEC93 --> STRIDE4
STRIDE1 --> TA1
STRIDE1 --> TA3
STRIDE4 --> TA1
linkStyle 0 stroke:#FF0000,stroke-width:2px
linkStyle 1 stroke:#FF0000,stroke-width:2px
linkStyle 2 stroke:#FFA500,stroke-width:2px
linkStyle 3 stroke:#FF0000,stroke-width:2px
linkStyle 4 stroke:#FF0000,stroke-width:2px
linkStyle 5 stroke:#FFA500,stroke-width:2px
linkStyle 6 stroke:#FF0000,stroke-width:2px
linkStyle 7 stroke:#FF0000,stroke-width:2px
linkStyle 8 stroke:#FFA500,stroke-width:2px
linkStyle 9 stroke:#FF0000,stroke-width:2px
linkStyle 10 stroke:#FF0000,stroke-width:2px
linkStyle 11 stroke:#FFA500,stroke-width:2px
SC-2: VtaClient / ClientIdentity (vta_sdk)
---
config:
layout: dagre
look: classic
theme: dark
---
flowchart LR
subgraph SL1["1. System Component"]
direction LR
SC2@{ shape: rect, label: "SC-2: VtaClient / ClientIdentity (vta_sdk)" }
end
subgraph SL2["2. Weaknesses"]
direction LR
CWE316@{ shape: rect, label: "CWE-316: Cleartext Storage of Sensitive Info in Memory" }
CWE345@{ shape: rect, label: "CWE-345: Insufficient Verification of Data Authenticity" }
CWE20@{ shape: rect, label: "CWE-20: Improper Input Validation" }
end
subgraph SL3["3. Attack Patterns"]
direction LR
CAPEC37@{ shape: rect, label: "CAPEC-37: Retrieve Embedded Sensitive Data" }
CAPEC176@{ shape: rect, label: "CAPEC-176: Configuration/Environment Manipulation" }
CAPEC153@{ shape: rect, label: "CAPEC-153: Input Data Manipulation" }
end
subgraph SL4["4. Threats"]
direction LR
STRIDE2@{ shape: rect, label: "STRIDE-2: Private Key Multibase Exposure via ClientIdentity Construction<br><i>High / Possible</i>" }
STRIDE6@{ shape: rect, label: "STRIDE-6: DID Key Tampering via Unvalidated Fields<br><i>Medium / Possible</i>" }
STRIDE11@{ shape: rect, label: "STRIDE-11: Lack of Input Validation on private_key_multibase<br><i>Medium / Possible</i>" }
end
subgraph SL5["5. Threat Actors"]
direction LR
TA3@{ shape: rect, label: "TA-3: Local Host Compromise Actor<br><i>Extract private key material or cached tokens</i>" }
TA1@{ shape: rect, label: "TA-1: Malicious CI Insider<br><i>Exfiltrate signing credentials or forge commit signatures</i>" }
end
SC2 --> CWE316
SC2 --> CWE345
SC2 --> CWE20
CWE316 --> CAPEC37
CWE345 --> CAPEC176
CWE20 --> CAPEC153
CAPEC37 --> STRIDE2
CAPEC176 --> STRIDE6
CAPEC153 --> STRIDE11
STRIDE2 --> TA3
STRIDE6 --> TA1
STRIDE11 --> TA1
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
SC-3: Local Token Cache Store
---
config:
layout: dagre
look: classic
theme: dark
---
flowchart LR
subgraph SL1["1. System Component"]
direction LR
SC3@{ shape: rect, label: "SC-3: Local Token Cache Store" }
end
subgraph SL2["2. Weaknesses"]
direction LR
CWE613@{ shape: rect, label: "CWE-613: Insufficient Session Expiration" }
CWE367@{ shape: rect, label: "CWE-367: TOCTOU Race Condition" }
end
subgraph SL3["3. Attack Patterns"]
direction LR
CAPEC39@{ shape: rect, label: "CAPEC-39: Manipulating Opaque Client-based Data Tokens" }
CAPEC26@{ shape: rect, label: "CAPEC-26: Leveraging Race Conditions" }
end
subgraph SL4["4. Threats"]
direction LR
STRIDE3@{ shape: rect, label: "STRIDE-3: Unauthenticated Token Attachment via set_token<br><i>Medium / Possible</i>" }
STRIDE8@{ shape: rect, label: "STRIDE-8: TOCTOU Race Between Cached Token Load and Identity Binding<br><i>Low / Unlikely</i>" }
end
subgraph SL5["5. Threat Actors"]
direction LR
TA3@{ shape: rect, label: "TA-3: Local Host Compromise Actor<br><i>Extract private key material or cached tokens</i>" }
end
SC3 --> CWE613
SC3 --> CWE367
CWE613 --> CAPEC39
CWE367 --> CAPEC26
CAPEC39 --> STRIDE3
CAPEC26 --> STRIDE8
STRIDE3 --> TA3
STRIDE8 --> TA3
linkStyle 0 stroke:#FFA500,stroke-width:2px
linkStyle 1 stroke:#00FF00,stroke-width:2px
linkStyle 2 stroke:#FFA500,stroke-width:2px
linkStyle 3 stroke:#00FF00,stroke-width:2px
linkStyle 4 stroke:#FFA500,stroke-width:2px
linkStyle 5 stroke:#00FF00,stroke-width:2px
linkStyle 6 stroke:#FFA500,stroke-width:2px
linkStyle 7 stroke:#00FF00,stroke-width:2px
📊 Risk Summary
Total Threats: 11
By Severity: Low: 3 · High: 2 · Medium: 6
By Category: Spoofing: 3 · Tampering: 7 · Elevation of Privilege: 3 · Information Disclosure: 2 · Repudiation: 1 · Denial of Service: 2
🎯 Attack Surface
Kill Chain 1: An attacker with local write access to a CI runner or developer host (TA-1, TA-3) tampers with or replaces the cached token in SC-3 (Local Token Cache Store), then relies on authenticate() in vta.rs to unconditionally construct a fresh ClientIdentity from creds.credential_did/private_key_multibase/vta_did (STRIDE-6) and bind it via client.set_token(token) (STRIDE-1, STRIDE-3) without validating that the token's original subject matches the newly constructed identity; if the VTA mediator's server-side validation is lenient, this chain enables authentication confusion and potential unauthorized commit signing. Kill Chain 2: An attacker who achieves local memory-read capability (TA-3) on the host running authenticate() can extract creds.private_key_multibase during ClientIdentity construction (STRIDE-2) due to insufficient zeroization guarantees on SDK-internal identity objects, then combine this with malformed-input handling gaps (STRIDE-11) to potentially trigger SDK panics or exfiltrate raw key material, ultimately compromising the DID private key used to sign all future commits under that identity. Kill Chain 3: A supply-chain attacker (TA-2) compromises the vta_sdk crate's build/publish pipeline (STRIDE-7), injecting malicious logic into ClientIdentity::did_key or VtaClient::set_token that silently exfiltrates key material or forges valid-looking authentication state; because did-git-sign trusts vta_sdk's authentication primitives implicitly and lacks pinned/verified dependency provenance, this compromise would propagate undetected to every consumer relying on the SDK, directly undermining the verifiable-git-infrastructure's core trust guarantee. Kill Chain 4: Configuration tampering (STRIDE-6, STRIDE-9) allows an attacker to null out creds.mediator_did, forcing authenticate() into the weaker cached-token fast path (line 27-38) instead of a presumably more rigorous mediator-negotiated flow, then chains into STRIDE-1/STRIDE-3 to complete an authentication downgrade attack that is further obscured by the absence of audit logging (STRIDE-4) on this code path, delaying detection and remediation.
🛡️ Risk Mitigation Strategy
Priority 1 (Immediate): Address the identity-token binding gap in authenticate() (RISK-001) by requiring that cached tokens carry a verifiable subject claim matching creds.credential_did/vta_did, and reject mismatches before calling client.set_token; this directly closes the highest-severity authentication confusion vector (STRIDE-1) and reduces the value of the mediator_did downgrade path (STRIDE-9). Priority 2 (Short-Term): Harden private key handling (RISK-002) by auditing vta_sdk's ClientIdentity for ZeroizeOnDrop coverage and adding local multibase format validation prior to SDK calls, preventing both memory-disclosure (STRIDE-2) and panic-based denial-of-service (STRIDE-11) scenarios; in parallel, add DID format/allowlist validation and configuration integrity checks (RISK-004) to prevent tampering with credential_did/vta_did/mediator_did fields (STRIDE-6). Priority 3 (Medium-Term): Improve forensic readiness (RISK-005) by instrumenting the cached-token authentication path with structured, tamper-evident audit logging that captures did_key_id, token fingerprint, and timestamp without leaking sensitive values into general error output (STRIDE-4, STRIDE-10). Priority 4 (Medium-to-Long-Term): Reduce concentrated supply-chain risk (RISK-003) by pinning the internal vta_sdk dependency to verified, checksummed revisions, introducing SBOM/provenance attestation for its build pipeline, and requiring mandatory security review for any changes to authentication primitives (ClientIdentity, VtaClient), since a compromise here would undermine every mitigation implemented at the application layer.
Generated by Agentic Sec — Threat Model & Affect Analysis Agent
📊 Summary & findings
| ✅ Confirmed | |
|---|---|
| 0 | 1 |
Must-Review-By-Human (1)
- 🔵 Cached token attached without local validation of expiry/signature before use
Fixes: