<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Extella</title>
    <description>The latest articles on DEV Community by Extella (@extella_os).</description>
    <link>https://dev.to/extella_os</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4212425%2Fcf94e9ba-e2c2-475e-a3d7-fd7fca332525.png</url>
      <title>DEV Community: Extella</title>
      <link>https://dev.to/extella_os</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/extella_os"/>
    <language>en</language>
    <item>
      <title>API keys and agents: how approval before retrieval works</title>
      <dc:creator>Extella</dc:creator>
      <pubDate>Wed, 28 Oct 2026 08:12:32 +0000</pubDate>
      <link>https://dev.to/extella_os/api-key-leaks-what-to-check-in-an-ai-coding-project-1jn6</link>
      <guid>https://dev.to/extella_os/api-key-leaks-what-to-check-in-an-ai-coding-project-1jn6</guid>
      <description>&lt;p&gt;In May 2026, an administrative key for LiteLLM's internal agent platform appeared in a public repository. The team deleted the file but did not revoke the key. An attacker used it in October, extracted other credentials and gained access to the release pipeline. The sequence is documented in &lt;a href="https://docs.litellm.ai/blog/security-incident-oct-21-2026-attack-chain" rel="noopener noreferrer"&gt;LiteLLM's investigation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Revoking the exposed key would have stopped that credential from working. Removing the file did not. &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository" rel="noopener noreferrer"&gt;GitHub's guidance&lt;/a&gt; puts revocation or rotation before cleaning up repository history.&lt;/p&gt;

&lt;p&gt;Preventing the next copy from leaking is a separate problem. In a project built with an AI agent, a key can enter the model's context through a chat message or a file-reading tool. Excluding &lt;code&gt;.env&lt;/code&gt; from Git does not stop a program with file access from reading it.&lt;/p&gt;

&lt;p&gt;The agent can write code using a key name. The running program still needs a way to authenticate its API requests.&lt;/p&gt;

&lt;p&gt;In Extella, where agents can build and run programs, we use KV vault to separate those steps. The agent works with the name of a saved key. A person enters its value in a desktop window and approves each new request to retrieve it. This adds a decision before the program receives the secret, with consequences for both security and unattended work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Saving the value outside the chat
&lt;/h2&gt;

&lt;p&gt;The application starts the save flow with &lt;code&gt;POST /api/desktop/kv/save&lt;/code&gt;, supplying a suggested key name, an application name and a reason.&lt;/p&gt;

&lt;p&gt;Extella Desktop opens a separate input window. The user enters the secret there. Desktop encrypts the value on the device before sending the record to KV storage, which stores values under names such as &lt;code&gt;openai_api_key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The encryption key is derived from a PIN-secret held in Desktop on the user's device. That PIN-secret is not stored with the KV record. The application receives the saved name and a status, without the secret value. During this save flow, the plaintext does not pass through the model or the OS server.&lt;/p&gt;

&lt;p&gt;The program can now contain a reference to &lt;code&gt;openai_api_key&lt;/code&gt;. Copying that source file copies the name. This depends on the program keeping the value out of source files, logs and generated output when it later retrieves it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asking for access
&lt;/h2&gt;

&lt;p&gt;The program authenticates to the OS API and calls &lt;code&gt;POST /api/desktop/kv/request&lt;/code&gt; with the key name, an application name and a reason. The credentials used to authenticate this request need protection too; moving one API key into a vault does not remove every credential from the application.&lt;/p&gt;

&lt;p&gt;Desktop displays the request and offers Allow or Deny. The user is approving the release of the value to the requesting program, not just revealing it on screen. A new retrieval requires a new approval. Another program knowing the key name is not enough for it to retrieve the value through this API.&lt;/p&gt;

&lt;p&gt;The application name and reason come from the request. They help the user understand what is being asked, but do not verify the identity or behaviour of the code. A convincing explanation in a prompt is not proof that the program will use the key as described.&lt;/p&gt;

&lt;p&gt;While waiting, the program polls &lt;code&gt;GET /api/desktop/kv/requests/{id}&lt;/code&gt;. If the user declines or the request expires, the program should stop and report that outcome. It should not keep opening new requests until the user accepts one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens after Allow
&lt;/h2&gt;

&lt;p&gt;Desktop decrypts the value on the device. The plaintext then passes through Extella OS server memory to the requesting program.&lt;/p&gt;

&lt;p&gt;The first poll after approval receives the value. The server removes its pending copy, and later polls report &lt;code&gt;delivered&lt;/code&gt;. An approved value that the program never retrieves expires after 60 seconds. A request that receives no answer expires after 180 seconds.&lt;/p&gt;

&lt;p&gt;These are delivery time limits. They do not make the underlying API key expire, and they cannot stop a recipient from keeping its own copy.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Where the usable secret is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;User saves the key&lt;/td&gt;
&lt;td&gt;Desktop handles the plaintext and encrypts it before storage.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key is stored&lt;/td&gt;
&lt;td&gt;KV contains ciphertext; the PIN-secret stays in Desktop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Program requests access&lt;/td&gt;
&lt;td&gt;Knowing the name does not grant retrieval; the request waits for the user's decision.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User approves&lt;/td&gt;
&lt;td&gt;Desktop decrypts the value; OS server memory and the receiving process handle the plaintext.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delivery completes&lt;/td&gt;
&lt;td&gt;The server removes its pending copy. The receiving process may still hold the value.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The program uses the key to call the external API and returns the result of its task. It must keep the secret out of that result. If it prints the key or includes it in a response to the agent, the model can see it despite the earlier approval step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes, and what it leaves exposed
&lt;/h2&gt;

&lt;p&gt;The design separates access to project files from access to the stored secret. An agent can read the project and work with a key name without finding the value in &lt;code&gt;.env&lt;/code&gt;. A copy of the KV record contains ciphertext rather than a ready-to-use API credential.&lt;/p&gt;

&lt;p&gt;That distinction does not establish that the whole system is safe from compromise. Desktop must protect its PIN-secret and handle approval. The OS server handles plaintext during delivery. The receiving program can expose it through memory, logs, files or its output. Those components remain part of the system that has to be trusted.&lt;/p&gt;

&lt;p&gt;Approval also does not change the permissions granted by the external service. If a key has administrator rights, Allow hands the program a credential with those rights. A program that reads one table should have a credential limited to that task. &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html" rel="noopener noreferrer"&gt;OWASP's secrets-management guidance&lt;/a&gt; covers access restrictions, expiration and revocation alongside storage.&lt;/p&gt;

&lt;p&gt;Once someone has copied a plaintext key, denying later vault requests cannot revoke it. The issuing service must invalidate it. If an attacker still controls the runtime, replacement keys may be stolen too; the operator needs to remove that access before restoring secrets to the environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost of requiring a person
&lt;/h2&gt;

&lt;p&gt;Each new retrieval depends on an open Desktop session and a person answering the request. That fits a session where someone is present to review what the program is doing. It does not fit a job that must obtain fresh access at 3 a.m. with nobody available.&lt;/p&gt;

&lt;p&gt;Keeping a plaintext key in a long-running process may avoid another retrieval, but then the process continues to hold the credential. The approval window does not give the user control over every subsequent API call made with that copy.&lt;/p&gt;

&lt;p&gt;An application hosted elsewhere also needs code changes to use this retrieval flow. It can avoid storing the external API key in that host's environment settings, but the host can still capture the key once the running application receives it. The integration does not make a compromised host trustworthy.&lt;/p&gt;

&lt;p&gt;For an implementation review, follow the value through one complete request: entry, encrypted storage, approval, delivery and use. Use a fake secret in a disposable configuration to check whether it appears in tool output or logs. Test refusal and timeout handling as well. Such checks cover the paths exercised; they do not amount to a security audit of the vault.&lt;/p&gt;




&lt;p&gt;Prepared with AI assistance.&lt;/p&gt;

</description>
      <category>security</category>
      <category>api</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
