September 26, 2026
CVE-2026-17070: Crossing the Liman Vault credential boundary
A personal-key operation changed shared credential state and selected another user’s identity for an already-permitted function.

By Ahmet Sadık Şahiner
5 min read
How a personal-key operation changed shared credential state and caused an already-permitted function to run under another user's managed-server identity.
Editorial explainer: the credential-sharing boundary.
Ahmet Sadık Şahiner · Independent Security Researcher & Information Technology Specialist
Research record: CVE-2026–17070 · HAVELSAN Liman MYS · CWE-862 · CVSS v3.1 8.8 High (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H). The official affected range is 2.2.3 through versions before 2.3.1. Preserved strengthened test: Core release.master.1110, Fiber release.72. Public references checked 26 September 2026. Official CVE record.
TL;DR
The important boundary was credential ownership and sharing. The low-privilege actor already had access to the target server object and permission to invoke one ordinary extension function. It had no global server-management permissions. The normal sharing-management path rejected the actor, but the personal Vault key-creation path accepted a shared value that changed server-wide state.
After the actor deleted its own temporary key, the sharing flag remained enabled. The credential resolver then selected another user's remaining key. The same allowed extension function ran in that key owner's managed-server context. Preserved tests demonstrate both a non-root identity and a root identity in separate controlled configurations.
Fixed-version scope: the CVE record identifies 2.3.1 as the boundary. The supplied package does not contain a full retest of this strengthened, already-authorized-server scenario on 2.3.1. The record's declaration and that exact-chain retest are separate claims.
Threat model and source identity
The source lock identifies Core commit 42157362cbbc0dad9f798ba0e3382c83fe62d5c6 and Fiber commit 659be94ec64cc61d40eee4bbe491fbac3a496a8a. The tests ran in a local Docker lab with synthetic users and a disposable SSH target.
The actor had one server object and one extension object, with zero add_server, update_server or server_details permissions. A marker-only remoteIdentity extension function was installed as a lab fixture and explicitly granted. The product API, credential resolver and normal extension-render route were used. The separate raw /engine/command path was not invoked.
This setup isolates the credential issue: the question is whose credential an allowed operation uses, rather than whether the caller can invoke that operation.
Root cause: three decisions became one
First, VaultController::createKey() used the caller's personal-key workflow to update servers.shared_key from request-controlled shared state. Access to an object and authority to share credentials for that object are different permissions. The normal server-management route enforced a boundary that this path did not.
Second, deleting the temporary personal key did not reverse the global sharing flag. The actor could leave no personal key while the server still advertised shared-key availability.
Third, the captured Fiber resolver first searched for a key belonging to the caller and server. If none was present and SharedKey == 1, it selected a key for the server and changed encrypterUser to that key's owner. The plaintext secret did not need to be returned to the actor: selecting and using the other identity was enough to cross the boundary.
Controlled proof and false-positive controls
The 15 July 2026 root-impact record preserves the following controls and state transitions:
- Unauthenticated Vault mutation: HTTP 401.
- The actor's normal server-sharing update: HTTP 403.
- Actor global management permission count: 0.
- Before the state change: the permitted function could not connect, and no canary existed.
- Vault key creation with the hidden sharing value: HTTP 200.
- Deletion of the actor's own temporary key: HTTP 200.
- After deletion: sharing flag 1, actor key count 0, administrator key count 1.
- The same permitted function then returned HTTP 200 and the administrator credential's remote identity.
The before-state response needs careful reading: the outer HTTP status was 201, while its body contained status: 403 and cannot connect to server. It is not accurate to describe that particular response as an HTTP 403. The normal sharing-management request is the separate HTTP 403 control.
Identity selection determines the impact
With the original non-root victim configuration, the function returned:
RemoteIdentity=uid=1000(audit) gid=100(users) groups=100(users)
CanaryMetadata=audit:users 600
overall=PASSRemoteIdentity=uid=1000(audit) gid=100(users) groups=100(users)
CanaryMetadata=audit:users 600
overall=PASSFor the managed-root variant, the synthetic administrator had a root credential for a separate disposable SSH container. Both recorded iterations produced a root identity and CanaryMetadata=root:root 600. The root credential and SSH policy were temporary lab fixtures, and the password was not printed.
The paired results support credential confusion as the mechanism. They do not imply that all installed extensions expose arbitrary shell execution. The specific permitted marker function ran under the selected victim identity. Root impact depends on a root-capable credential and the capabilities of the permitted operation.
The restoration record reports removal of temporary permissions, keys, helpers and canaries and restoration of the extension and SSH state. A post-restoration non-root run again returned the audit identity. The recorded original sharing flag was 1; each attack baseline deliberately set it to 0. Restoration must be evaluated against the original fixture state, not confused with the per-test baseline.
The 2.3.1 fix and the untested boundary
The 27 July 2026 fix added server-object access checks to create() and createKey(), and changed the recorded version to 2.3.1. The release and CVE record establish that fixed-version declaration.
The strengthened test actor, however, already possessed server-object access. Source inspection of a newly added object-access check does not substitute for running this exact credential-sharing chain against the fixed version. This article neither declares 2.3.1 vulnerable to that chain nor claims the chain was dynamically closed there.
Later architectural hardening
Commit e8873b27, dated 2 September 2026, separately introduced shared-key security changes. The inspected diff includes a dedicated share_server_key permission, key-level sharing state, an explicit sharing route, transaction/row locking, state maintenance and audit-related changes.
These changes are relevant to the underlying ownership boundary. They are later work and should not be described as the July 2.3.1 patch. Inspection of the Core diff also does not establish end-to-end enforcement by every deployed credential consumer.
Defensive lessons
Treat credential sharing as its own authorized operation. Bind a grant to a particular key and owner, make revocation atomic with deletion, and ensure the resolver consumes that explicit grant. Auditing should preserve both the calling application identity and the identity selected for the managed target.
The key question is not only "Can this user access this server?" It is also "Can this user cause this other person's credential to be selected?"
Timeline and disclosure scope
- 15 July 2026: preserved authorized-function, root-impact and restoration records.
- 27 July 2026: object-access fix and 2.3.1 release.
- 4 August 2026: CVE publication and researcher credit.
- 2 September 2026: later shared-key hardening commit.
The evidence concerns isolated synthetic lab identities. It does not demonstrate plaintext credential disclosure, unauthenticated exploitation, a production compromise or a fresh 2.3.1 retest.
References
- Official CVE record
- July Vault authorization fix
- 2.3.1 release
- September shared-key hardening
- Evidence: controlled laboratory records retained by the author; selected results are shown below.
Related research
See the separate Liman package-queue finding for another boundary with a distinct trust contract.
Evidence and technical diagrams
The proof panel shows controlled identity and authorization results. The other figures explain the credential boundary and the distinct patch stages.
Real evidence: rejected unauthenticated and normal-management requests, changed sharing state, and non-root/root identities in controlled tests.
Conceptual attack chain: a personal Vault key operation changes sharing; deleting the caller key exposes another owner's credential selection.
Conceptual root cause: a personal key mutation and a server-wide sharing flag affect cross-user credential fallback.
Conceptual patch comparison: July server-object checks and September explicit key-sharing controls are separate changes. Exact-chain 2.3.1 retesting remains absent.