<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Security on TorchTree</title>
        <link>https://torchtree.com/en/tags/security/</link>
        <description>Recent content in Security on TorchTree</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
        <copyright>TorchTree Co., Ltd.</copyright>
        <lastBuildDate>Fri, 04 Sep 2026 23:00:00 +0800</lastBuildDate><atom:link href="https://torchtree.com/en/tags/security/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Storing API Keys in macOS Keychain</title>
        <link>https://torchtree.com/en/post/macos-keychain-api-key-cli/</link>
        <pubDate>Fri, 04 Sep 2026 23:00:00 +0800</pubDate>
        
        <guid>https://torchtree.com/en/post/macos-keychain-api-key-cli/</guid>
        <description>&lt;p&gt;On macOS we read and write Keychain with the &lt;code&gt;security&lt;/code&gt; command to store API keys. This article expands the Keychain section of &lt;a class=&#34;link&#34; href=&#34;https://torchtree.com/en/post/api-key-storage-best-practices/&#34; &gt;&amp;ldquo;API Keys Don&amp;rsquo;t Belong in Your Shell Config Files&amp;rdquo;&lt;/a&gt;, focusing on command-line CRUD.&lt;/p&gt;
&lt;h2 id=&#34;gui-changes&#34;&gt;GUI changes
&lt;/h2&gt;&lt;p&gt;Since macOS 15 Sequoia, website logins, Wi-Fi passwords, and passkeys moved into Passwords, while Keychain Access moved to the Other folder. Spotlight searches for Keychain Access redirect to Passwords.&lt;/p&gt;
&lt;p&gt;Passwords covers only those three categories and shows no generic application passwords. Under Sequoia, creating a Secure Note fails: New brings up only a New Password dialog, and the File &amp;gt; New Password Item flow does not respond.&lt;/p&gt;
&lt;p&gt;Generic items like API keys have no corresponding GUI entry. The command line is the working entry point.&lt;/p&gt;
&lt;h2 id=&#34;item-type&#34;&gt;Item type
&lt;/h2&gt;&lt;p&gt;API keys live in &lt;code&gt;generic password&lt;/code&gt; items, located by service, account, and password:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;service&lt;/code&gt; is the project or app name, e.g. &lt;code&gt;MyApp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;account&lt;/code&gt; is the key name, e.g. &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;password&lt;/code&gt; is the key value&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;internet password&lt;/code&gt; carries extra fields such as &lt;code&gt;server&lt;/code&gt;, &lt;code&gt;protocol&lt;/code&gt;, and &lt;code&gt;port&lt;/code&gt;. It serves website logins, not API keys.&lt;/p&gt;
&lt;h2 id=&#34;command-line-crud&#34;&gt;Command-line CRUD
&lt;/h2&gt;&lt;p&gt;We take the login keychain as the default target. Commands without an explicit keychain file read and write it.&lt;/p&gt;
&lt;h3 id=&#34;create&#34;&gt;Create
&lt;/h3&gt;&lt;p&gt;Omitting the value after &lt;code&gt;-w&lt;/code&gt; switches to interactive input, keeping the secret out of shell history:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security add-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -w
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Use label and comment for notes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security add-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -l &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp OpenAI&amp;#34;&lt;/span&gt; -j &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;prod key 2026-09&amp;#34;&lt;/span&gt; -w
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;read&#34;&gt;Read
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;-w&lt;/code&gt; prints only the password value, &lt;code&gt;-g&lt;/code&gt; prints the full item with the password:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security find-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -w
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security find-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -g
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;update&#34;&gt;Update
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;-U&lt;/code&gt; overwrites when the item already exists. Without &lt;code&gt;-U&lt;/code&gt;, the command errors on an existing item:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security add-generic-password -U -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -w
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Without &lt;code&gt;-U&lt;/code&gt;, delete-then-add reaches the same result:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security delete-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security add-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -w
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;delete-and-list&#34;&gt;Delete and list
&lt;/h3&gt;&lt;p&gt;Delete one item and list all items:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security delete-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security dump-keychain | grep -A4 &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;svce&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;dump-keychain&lt;/code&gt; output covers all items with metadata.&lt;/p&gt;
&lt;h2 id=&#34;calling-from-scripts-and-code&#34;&gt;Calling from scripts and code
&lt;/h2&gt;&lt;p&gt;Shell reads once at startup and injects into an environment variable. The secret is never written to a file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;export OPENAI_API_KEY&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;security find-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -w&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Python calls Keychain through &lt;code&gt;keyring&lt;/code&gt;, with &lt;code&gt;service&lt;/code&gt; and &lt;code&gt;account&lt;/code&gt; matching the command-line triplet:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; keyring
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;keyring&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;set_password(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sk-...&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;api_key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; keyring&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_password(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Native development calls the Security framework, querying with &lt;code&gt;kSecClassGenericPassword&lt;/code&gt; plus &lt;code&gt;kSecAttrService&lt;/code&gt; and &lt;code&gt;kSecAttrAccount&lt;/code&gt;. Node calls through &lt;code&gt;keytar&lt;/code&gt;, Go through &lt;code&gt;go-keychain&lt;/code&gt;, with identical field semantics.&lt;/p&gt;
&lt;h2 id=&#34;practical-notes&#34;&gt;Practical notes
&lt;/h2&gt;&lt;p&gt;Practical notes fall into three places: history leakage, authorization popup, and sync scope.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First, history leakage.&lt;/strong&gt; &lt;code&gt;security add-generic-password -w sk-...&lt;/code&gt; writes the secret into shell history and the process table. We avoid plaintext in history and the process table with interactive input or &lt;code&gt;security -i&lt;/code&gt; interactive mode.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second, authorization popup.&lt;/strong&gt; Items land in the login keychain, which stays unlocked after login. The creating process reads directly, while other apps trigger a system authorization dialog on first read. Clicking Allow Always silences it afterward. CI or SSH hosts with no GUI session cannot click the dialog away. Pre-scope the allowed app with &lt;code&gt;-T /Applications/xxx.app&lt;/code&gt;, or switch to a dedicated keychain file. &lt;code&gt;-A&lt;/code&gt; allows any app to read, leaving access unrestricted.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Third, sync scope.&lt;/strong&gt; iCloud-synced items follow the account across machines. High-privilege API keys stay in the local login keychain and migrate with the keychain file.&lt;/p&gt;
&lt;h2 id=&#34;references&#34;&gt;References
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://discussions.apple.com/thread/255764804&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Apple community thread on Sequoia failing to create Secure Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://forums.macrumors.com/threads/keychain-app-is-it-still-in-macos-sequoia.2434342/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;MacRumors forum confirming Keychain Access still ships but hidden in Sequoia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.unix.com/man_page/osx/1/security/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Archived man page for the security command&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.netmeister.org/blog/keychain-passwords.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Jan Schaumann on managing command-line passwords with Keychain&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
    </channel>
</rss>
