Skip to content

Instantly share code, notes, and snippets.

@subtleGradient
Created May 14, 2026 16:46
Show Gist options
  • Select an option

  • Save subtleGradient/bdebaf0a5e6fd36f6326b715a49380e3 to your computer and use it in GitHub Desktop.

Select an option

Save subtleGradient/bdebaf0a5e6fd36f6326b715a49380e3 to your computer and use it in GitHub Desktop.
macOS Keychain Trust Reset Checklist
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>macOS Keychain Trust Reset Checklist</title>
<style>
:root {
color-scheme: light dark;
--bg: #0b1020;
--panel: #111827;
--panel-2: #1f2937;
--text: #e5e7eb;
--muted: #a7b0c0;
--line: #374151;
--accent: #93c5fd;
--green: #86efac;
--yellow: #fde68a;
--red: #fca5a5;
--code: #020617;
--purple: #c4b5fd;
}
@media (prefers-color-scheme: light) {
:root {
--bg: #f8fafc;
--panel: #ffffff;
--panel-2: #eef2ff;
--text: #0f172a;
--muted: #475569;
--line: #cbd5e1;
--accent: #1d4ed8;
--green: #166534;
--yellow: #854d0e;
--red: #991b1b;
--code: #0f172a;
--purple: #5b21b6;
}
}
* { box-sizing: border-box; }
body {
margin: 0;
background: radial-gradient(circle at top left, rgba(147, 197, 253, 0.13), transparent 32rem), var(--bg);
color: var(--text);
font: 16px/1.55 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
main {
max-width: 1040px;
margin: 0 auto;
padding: 32px 18px 72px;
}
h1, h2, h3 { line-height: 1.2; }
h1 { margin: 0 0 12px; font-size: clamp(2rem, 5vw, 3.6rem); letter-spacing: -0.04em; }
h2 { margin: 36px 0 12px; font-size: 1.55rem; }
h3 { margin: 22px 0 10px; font-size: 1.12rem; }
p { margin: 10px 0; }
a { color: var(--accent); }
.summary, .step, .gate, .danger, .note, .bias {
border: 1px solid var(--line);
border-radius: 16px;
padding: 16px;
background: var(--panel);
margin: 16px 0;
}
.summary { background: linear-gradient(135deg, var(--panel), var(--panel-2)); }
.danger { border-color: var(--red); }
.gate { border-color: var(--yellow); }
.note { border-color: var(--accent); }
.bias { border-color: var(--purple); }
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
gap: 12px;
margin: 14px 0;
}
.card {
border: 1px solid var(--line);
border-radius: 14px;
background: rgba(255, 255, 255, 0.03);
padding: 14px;
}
.checklist {
display: grid;
gap: 10px;
margin: 12px 0 0;
padding: 0;
list-style: none;
}
.checklist li {
display: grid;
grid-template-columns: 24px 1fr;
gap: 10px;
align-items: start;
}
input[type="checkbox"] {
width: 18px;
height: 18px;
margin-top: 3px;
accent-color: var(--accent);
}
code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
font-size: 0.95em;
}
pre {
overflow-x: auto;
border-radius: 12px;
border: 1px solid var(--line);
padding: 14px;
background: var(--code);
color: #e5e7eb;
font-size: 0.9rem;
line-height: 1.45;
}
table {
width: 100%;
border-collapse: collapse;
margin: 12px 0;
border: 1px solid var(--line);
border-radius: 12px;
overflow: hidden;
}
th, td {
border-bottom: 1px solid var(--line);
padding: 10px 12px;
text-align: left;
vertical-align: top;
}
th { background: var(--panel-2); }
tr:last-child td { border-bottom: 0; }
.status {
display: inline-block;
padding: 2px 8px;
border-radius: 999px;
font-weight: 800;
letter-spacing: 0.02em;
}
.green { color: var(--green); }
.yellow { color: var(--yellow); }
.red { color: var(--red); }
.purple { color: var(--purple); }
.muted { color: var(--muted); }
.small { font-size: 0.92rem; }
</style>
</head>
<body>
<main>
<h1>macOS Keychain Trust Reset Checklist</h1>
<section class="summary">
<p><strong>Goal:</strong> after the local dead-man path is inert, enumerate macOS Keychain trust, remove every unknown persistent grant, and stop treating absence of one attack indicator as proof of safety.</p>
<p><strong>Core invariant:</strong> a Mac is not green because one scan found nothing. It is green only when high-value keychain items have been reviewed, unknown grants are removed, broad access is gone, and provider-side credentials are rotated from a clean device.</p>
<p class="muted small">This template is intended to be public-safe before customization. Do not publish customized copies containing evidence logs, keychain dumps, usernames, hostnames, tokens, account identifiers, or real paths.</p>
</section>
<section class="danger">
<h2>Hard Stops</h2>
<ul class="checklist">
<li><input type="checkbox"><span>Run this only after the <code>gh-token-monitor</code> dead-man LaunchAgent and editor/package hooks have been neutralized. If token revocation can still trigger a local wipe, go back to the dead-man triage first.</span></li>
<li><input type="checkbox"><span>Keep the Mac offline until you intentionally reach the network re-enable gate.</span></li>
<li><input type="checkbox"><span>Do not run <code>security dump-keychain -d</code>. The <code>-d</code> flag dumps decrypted secret data. This checklist uses <code>-a</code> for access-control metadata only.</span></li>
<li><input type="checkbox"><span>Do not publish evidence output. ACL dumps can expose sensitive metadata even when they do not include passwords.</span></li>
<li><input type="checkbox"><span>Do not mark the host safe because you did not find <code>gh-token-monitor</code>. This checklist is about all persistent keychain trust, not one malware name.</span></li>
<li><input type="checkbox"><span>Do not leave valid exposed credentials alive upstream. Local keychain cleanup reduces future access; it does not undo exfiltration.</span></li>
</ul>
</section>
<section class="bias">
<h2>Bias Guardrails</h2>
<div class="grid">
<div class="card"><p><span class="status red">RED</span> Unknown app, script, shell, interpreter, package manager, editor helper, temp-path binary, unsigned binary, or broad "allow all applications" access on a high-value item.</p></div>
<div class="card"><p><span class="status yellow">YELLOW</span> Anything unreviewed, confusing, unavailable, or explained only by "I probably installed that." Unknown is not benign.</p></div>
<div class="card"><p><span class="status green">GREEN</span> The trusted app is recognized, currently needed, code-signed by an expected vendor, and expected to access that exact item.</p></div>
</div>
<p><strong>Anti-normalcy rule:</strong> if a fact makes you want to stop investigating, ask whether it proves safety or merely proves one narrow bad thing was absent.</p>
</section>
<section class="step">
<h2>1. Choose The Operating Mode</h2>
<p>Some checks work from Recovery against a mounted Data volume. Keychain Access UI review normally requires an offline normal boot into the user account after dead-man containment is green.</p>
<ul class="checklist">
<li><input type="checkbox"><span><strong>Recovery mode:</strong> use this for file inventory, quarantine, and pre-login evidence capture.</span></li>
<li><input type="checkbox"><span><strong>Offline normal mode:</strong> use this for Keychain Access UI review, user keychain ACL review, login items, TCC privacy settings, and logs.</span></li>
<li><input type="checkbox"><span><strong>Clean-device mode:</strong> use this for provider-side token revocation and account recovery after local dead-man risk is neutralized.</span></li>
</ul>
<div class="gate">
<p><span class="status yellow">YELLOW</span> If you cannot tell which mode you are in, stop. Do not run commands against Recovery's <code>~</code> while thinking it is the compromised user's home.</p>
</div>
</section>
<section class="step">
<h2>2. Create A Private Evidence Folder</h2>
<p>Keep evidence local. Do not publish it. Do not put it in an editor, repo, package cache, LaunchAgent path, or synced folder.</p>
<div class="danger">
<p><strong>Evidence files are sensitive.</strong> Files under <code>$EVIDENCE</code> may contain usernames, hostnames, account IDs, organization names, repository names, profile identifiers, certificate names, keychain item labels, app paths, and credential metadata. Never paste, upload, gist, commit, or share them without redaction.</p>
</div>
<h3>Recovery path setup</h3>
<pre><code>DATA="/Volumes/Macintosh HD - Data"
TARGET_USER="your-short-username"
USER_HOME="$DATA/Users/$TARGET_USER"
SYSTEM_LIBRARY="$DATA/Library"
ROOT_HOME="$DATA/private/var/root"
EVIDENCE="$DATA/Users/Shared/keychain-trust-reset-$(date +%Y%m%d-%H%M%S)"
test -d "$USER_HOME" || exit 1
test -d "$SYSTEM_LIBRARY" || exit 1
mkdir -p "$EVIDENCE"
chmod 700 "$EVIDENCE"
printf 'Evidence folder: %s\n' "$EVIDENCE"</code></pre>
<h3>Offline normal boot setup</h3>
<pre><code>USER_HOME="$HOME"
SYSTEM_LIBRARY="/Library"
ROOT_HOME="/var/root"
EVIDENCE="$HOME/PrivateIncidentEvidence/keychain-trust-reset-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$EVIDENCE"
chmod 700 "$EVIDENCE"
printf 'Evidence folder: %s\n' "$EVIDENCE"</code></pre>
<ul class="checklist">
<li><input type="checkbox"><span>The evidence folder exists and is private.</span></li>
<li><input type="checkbox"><span>The evidence folder is not in a public gist, repository, cloud sync folder, package cache, editor hook path, or LaunchAgent path.</span></li>
<li><input type="checkbox"><span>The evidence folder is not synced by iCloud Drive, Dropbox, Google Drive, OneDrive, a backup agent, or an editor workspace.</span></li>
</ul>
</section>
<section class="step">
<h2>3. Inventory Keychains Without Dumping Secrets</h2>
<p>This captures which keychains exist and which ones macOS searches. It does not prove the contents are clean.</p>
<p>Use the <code>USER_HOME</code> and <code>SYSTEM_LIBRARY</code> variables from the previous step. In Recovery, these must point at the mounted Data volume, not Recovery's home or system library.</p>
<pre><code>security list-keychains -d user &gt; "$EVIDENCE/user-keychains.txt" 2&gt;&amp;1
security list-keychains -d system &gt; "$EVIDENCE/system-keychains.txt" 2&gt;&amp;1
security default-keychain -d user &gt; "$EVIDENCE/default-keychain.txt" 2&gt;&amp;1
security login-keychain &gt; "$EVIDENCE/login-keychain.txt" 2&gt;&amp;1
ls -laOe "$USER_HOME/Library/Keychains" "$USER_HOME"/Library/Keychains/* &gt; "$EVIDENCE/user-keychain-files.txt" 2&gt;&amp;1
ls -laOe "$SYSTEM_LIBRARY/Keychains" "$SYSTEM_LIBRARY"/Keychains/* &gt; "$EVIDENCE/system-keychain-files.txt" 2&gt;&amp;1</code></pre>
<p class="muted small">The <code>security list-keychains</code> output is most meaningful in offline normal mode. In Recovery, rely on mounted-volume file inventory and treat any unavailable keychain as yellow.</p>
<ul class="checklist">
<li><input type="checkbox"><span>User keychains were inventoried.</span></li>
<li><input type="checkbox"><span>System keychains were inventoried.</span></li>
<li><input type="checkbox"><span>Nested Local Items or iCloud keychain directories under <code>~/Library/Keychains/&lt;UUID&gt;/</code> were noted and not silently skipped.</span></li>
<li><input type="checkbox"><span>Any custom or unexpected keychain file is treated as <span class="status yellow">YELLOW</span> until reviewed.</span></li>
</ul>
</section>
<section class="step">
<h2>4. Dump ACL And Item Metadata, No Decrypted Secrets</h2>
<p><code>security dump-keychain -a</code> prints item metadata and access-control entries. It is useful for finding persistent trusted apps. It can still reveal item names, account names, servers, labels, paths, and other sensitive metadata, so keep it private.</p>
<pre><code>mkdir -p "$EVIDENCE/acl-dumps"
for kc in \
"$USER_HOME"/Library/Keychains/*.keychain-db \
"$USER_HOME"/Library/Keychains/*.keychain \
"$USER_HOME"/Library/Keychains/*/*.keychain-db \
"$USER_HOME"/Library/Keychains/*/*.keychain \
"$USER_HOME"/Library/Keychains/*/keychain-*.db \
"$SYSTEM_LIBRARY"/Keychains/*.keychain \
"$SYSTEM_LIBRARY"/Keychains/*.keychain-db
do
[ -e "$kc" ] || continue
safe_name=$(printf '%s' "$kc" | /usr/bin/sed 's#[^A-Za-z0-9._-]#_#g')
out="$EVIDENCE/acl-dumps/$safe_name.acl.txt"
printf 'Dumping ACL metadata for %s\n' "$kc" | tee -a "$EVIDENCE/acl-dump-actions.txt"
security dump-keychain -a "$kc" &gt; "$out" 2&gt;&amp;1
done</code></pre>
<div class="danger">
<p><strong>Do not add <code>-d</code>.</strong> The command <code>security dump-keychain -ad</code> can include decrypted passwords. That is evidence contamination and a new leak risk.</p>
</div>
<ul class="checklist">
<li><input type="checkbox"><span>ACL metadata dumps were captured for every accessible keychain.</span></li>
<li><input type="checkbox"><span>No decrypted secret dump was created.</span></li>
<li><input type="checkbox"><span>Local Items or iCloud keychain stores that could not be dumped were marked <span class="status yellow">YELLOW</span> and handled by upstream credential rotation.</span></li>
<li><input type="checkbox"><span>Any keychain that could not be dumped is marked <span class="status yellow">YELLOW</span>, not ignored.</span></li>
</ul>
</section>
<section class="step">
<h2>5. Search For Dangerous Persistent Grants</h2>
<p>This search finds grants that deserve manual review. It is not a proof of safety when it returns nothing.</p>
<pre><code>SUSPICIOUS='gh-token-monitor|router_init|tanstack_runner|router_runtime|setup\.mjs|IfYouRevoke|/tmp|/private/tmp|/var/folders|AppTranslocation|Terminal\.app|iTerm|/usr/bin/security|/bin/sh|/bin/bash|/bin/zsh|node|bun|deno|tsx|uvx|npm|pnpm|yarn|npx|pipx|python|python3|ruby|perl|curl|wget|nvm|fnm|asdf|mise|/gh$|GitHub CLI|Visual Studio Code|Code\.app|Cursor|Windsurf|Zed|Claude|Electron|Helper|/opt/homebrew|/usr/local'
for f in "$EVIDENCE"/acl-dumps/*.acl.txt
do
[ -e "$f" ] || continue
printf '\n===== %s =====\n' "$f" &gt;&gt; "$EVIDENCE/suspicious-acl-lines.txt"
/usr/bin/grep -nEi "$SUSPICIOUS" "$f" &gt;&gt; "$EVIDENCE/suspicious-acl-lines.txt" 2&gt;&amp;1
done</code></pre>
<table>
<thead><tr><th>Grant family</th><th>Default status</th><th>Reason</th></tr></thead>
<tbody>
<tr><td>Unknown app or binary</td><td><span class="status red">RED</span></td><td>You cannot reason about who can read the item.</td></tr>
<tr><td>Shells, Terminal, iTerm, <code>/usr/bin/security</code></td><td><span class="status red">RED</span></td><td>Generic execution hosts let many scripts inherit access.</td></tr>
<tr><td><code>node</code>, <code>bun</code>, package managers, editor helpers</td><td><span class="status red">RED</span></td><td>Supply-chain malware commonly runs through these paths.</td></tr>
<tr><td><code>/tmp</code>, <code>/private/tmp</code>, <code>/var/folders</code></td><td><span class="status red">RED</span></td><td>Transient paths are not stable trust anchors.</td></tr>
<tr><td>Known app, expected item, expected vendor signature</td><td><span class="status green">GREEN</span></td><td>Only after manual verification.</td></tr>
</tbody>
</table>
<ul class="checklist">
<li><input type="checkbox"><span>Every suspicious ACL line has a manual review note.</span></li>
<li><input type="checkbox"><span>Every ACL path outside <code>/Applications</code>, <code>/System/Applications</code>, and expected vendor-controlled locations is treated as <span class="status yellow">YELLOW</span> or <span class="status red">RED</span> until justified.</span></li>
<li><input type="checkbox"><span>No high-value item trusts a generic shell, interpreter, package manager, editor helper, or temp-path binary.</span></li>
<li><input type="checkbox"><span>No high-value item remains trusted to an app you cannot identify and justify.</span></li>
</ul>
</section>
<section class="step">
<h2>6. Review High-Value Items In Keychain Access</h2>
<p>Use the GUI because it lets you see and remove trusted applications for a specific item. Do this offline after the dead-man gate is green.</p>
<ul class="checklist">
<li><input type="checkbox"><span>Open <code>/Applications/Utilities/Keychain Access.app</code>.</span></li>
<li><input type="checkbox"><span>If needed, choose <code>Window &gt; Keychain Viewer</code> or show the sidebar.</span></li>
<li><input type="checkbox"><span>Select the <code>login</code> keychain, then inspect <code>Passwords</code>, <code>Secure Notes</code>, <code>My Certificates</code>, <code>Keys</code>, and <code>All Items</code>.</span></li>
<li><input type="checkbox"><span>For each item, open <code>Get Info</code>, then the <code>Access Control</code> tab.</span></li>
<li><input type="checkbox"><span>Remove unknown apps from the allowed applications list.</span></li>
<li><input type="checkbox"><span>Disable <code>Allow all applications to access this item</code> on high-value items.</span></li>
<li><input type="checkbox"><span>Prefer <code>Confirm before allowing access</code> for normal items.</span></li>
<li><input type="checkbox"><span>Use <code>Ask for Keychain password</code> for high-value tokens, private keys, and credentials you rarely use.</span></li>
<li><input type="checkbox"><span>Save changes, close the item, reopen it, and verify the setting actually persisted.</span></li>
</ul>
<div class="note">
<p>Apple documents <code>Allow Once</code> as one-time access and <code>Always Allow</code> as persistent future access for that app and item. The persistent grant is what you are hunting.</p>
</div>
</section>
<section class="step">
<h2>7. Sweep The High-Value Credential Families</h2>
<p>Do not sample a few familiar entries and call it done. Review the families most likely to contain developer secrets.</p>
<table>
<thead><tr><th>Family</th><th>Examples to search in Keychain Access</th><th>Healthy state</th></tr></thead>
<tbody>
<tr><td>GitHub</td><td><code>github</code>, <code>gh</code>, <code>GitHub CLI</code>, <code>git credential</code></td><td>Unknown ACL grants removed; tokens later revoked/rotated upstream.</td></tr>
<tr><td>npm and package registries</td><td><code>npm</code>, <code>node</code>, registry hostnames, package publisher tools</td><td>No package manager or shell has standing access to long-lived tokens.</td></tr>
<tr><td>SSH and signing keys</td><td><code>ssh</code>, private keys, certificate identities, signing identities</td><td>Private key use requires expected app and appropriate prompting.</td></tr>
<tr><td>Cloud and infra</td><td><code>aws</code>, <code>gcloud</code>, <code>azure</code>, <code>kubernetes</code>, <code>vault</code>, <code>docker</code></td><td>Unknown grants removed; cloud-side keys rotated from a clean device.</td></tr>
<tr><td>AI and editor tools</td><td><code>Claude</code>, <code>OpenAI</code>, <code>Anthropic</code>, <code>Copilot</code>, editor auth</td><td>No editor helper has unexplained broad access.</td></tr>
<tr><td>Browsers and app storage</td><td><code>Chrome Safe Storage</code>, <code>Safari</code>, <code>Firefox</code>, app-specific storage keys</td><td>Access is expected; browser sessions considered exposed until rotated.</td></tr>
<tr><td>Password managers</td><td>1Password, Bitwarden, Dashlane, KeePass, passkeys, recovery items</td><td>Only the password manager itself has expected access; account recovery reviewed.</td></tr>
</tbody>
</table>
<ul class="checklist">
<li><input type="checkbox"><span>Every credential family above was searched by name and by likely vendor/app strings.</span></li>
<li><input type="checkbox"><span>Any credential family you do not use was still searched, because malware may create decoy names.</span></li>
<li><input type="checkbox"><span>Any valid credential from the exposure window is scheduled for upstream revocation/rotation from a clean device.</span></li>
</ul>
</section>
<section class="step">
<h2>8. Review Adjacent macOS Trust Surfaces</h2>
<p>Keychain ACLs are not the whole trust boundary. Apps with broad local permissions or background execution can ask for keychain access again, read local secret files, or steal browser sessions.</p>
<ul class="checklist">
<li><input type="checkbox"><span><strong>Login Items:</strong> System Settings &gt; General &gt; Login Items. Disable unknown apps and background items.</span></li>
<li><input type="checkbox"><span><strong>Full Disk Access:</strong> System Settings &gt; Privacy &amp; Security &gt; Full Disk Access. Remove unknown developer tools, terminals, editors, package managers, and scripts.</span></li>
<li><input type="checkbox"><span><strong>Accessibility:</strong> remove unknown apps that can control the Mac.</span></li>
<li><input type="checkbox"><span><strong>Automation:</strong> remove unknown app-to-app control grants, especially anything controlling Terminal, browsers, editors, or Keychain Access.</span></li>
<li><input type="checkbox"><span><strong>Developer Tools:</strong> remove unknown tools allowed to run software locally.</span></li>
<li><input type="checkbox"><span><strong>VPN &amp; Device Management:</strong> remove or investigate unknown configuration profiles, MDM enrollment, root certificates, proxies, and VPNs.</span></li>
<li><input type="checkbox"><span><strong>Extensions:</strong> review browser extensions, Safari extensions, login extensions, and network extensions.</span></li>
</ul>
<p>Profile output is highly identifying. It can expose organization, MDM, certificate, proxy, VPN, Wi-Fi, email, and enrollment identifiers. Keep it private and redact before sharing.</p>
<pre><code>profiles status -type enrollment &gt; "$EVIDENCE/profile-enrollment.txt" 2&gt;&amp;1
profiles list &gt; "$EVIDENCE/profiles-list.txt" 2&gt;&amp;1
for d in \
"$USER_HOME/Library/LaunchAgents" \
"$SYSTEM_LIBRARY/LaunchAgents" \
"$SYSTEM_LIBRARY/LaunchDaemons" \
"$ROOT_HOME/Library/LaunchAgents" \
"$ROOT_HOME/Library/LaunchDaemons" \
"$USER_HOME/Library/PrivilegedHelperTools" \
"$SYSTEM_LIBRARY/PrivilegedHelperTools" \
"$USER_HOME/Library/Application Scripts" \
"$USER_HOME/Library/Containers" \
"$USER_HOME/.claude" \
"$USER_HOME/.vscode"
do
[ -d "$d" ] || continue
printf '\n===== %s =====\n' "$d" &gt;&gt; "$EVIDENCE/autoload-scan.txt"
ls -laOe "$d" &gt;&gt; "$EVIDENCE/autoload-scan.txt" 2&gt;&amp;1
/usr/bin/grep -RInE '/tmp|/private/tmp|/var/folders|curl|wget|osascript|node|bun|deno|tsx|uvx|npm|pnpm|yarn|npx|pipx|python|gh|security|keychain|ssh|vault|docker|kubernetes|SessionStart|folderOpen|setup\.mjs|router_runtime|router_init|tanstack_runner' "$d" &gt;&gt; "$EVIDENCE/autoload-scan.txt" 2&gt;&amp;1
done</code></pre>
<h3>Shell And Project Startup Hooks</h3>
<pre><code>for f in \
"$USER_HOME/.zshrc" \
"$USER_HOME/.zprofile" \
"$USER_HOME/.zlogin" \
"$USER_HOME/.bash_profile" \
"$USER_HOME/.bashrc" \
"$USER_HOME/.profile" \
"$USER_HOME/.config/fish/config.fish" \
"$USER_HOME/.gitconfig" \
"$USER_HOME/.npmrc"
do
[ -e "$f" ] || continue
printf '\n===== %s =====\n' "$f" &gt;&gt; "$EVIDENCE/startup-file-scan.txt"
/usr/bin/grep -nE '/tmp|/private/tmp|/var/folders|curl|wget|osascript|node|bun|deno|npm|pnpm|yarn|npx|gh|security|keychain|setup\.mjs|router_runtime|router_init|tanstack_runner' "$f" &gt;&gt; "$EVIDENCE/startup-file-scan.txt" 2&gt;&amp;1
done</code></pre>
<ul class="checklist">
<li><input type="checkbox"><span>Home-level Claude and VS Code hooks were checked before opening either tool.</span></li>
<li><input type="checkbox"><span>Repo-local <code>.claude</code>, <code>.vscode/tasks.json</code>, package scripts, and lifecycle hooks are treated as <span class="status yellow">YELLOW</span> until checked before opening project folders.</span></li>
<li><input type="checkbox"><span>Shell startup files and user-level config files do not invoke unknown network tools, shells, package managers, or credential commands.</span></li>
</ul>
<div class="gate">
<p><span class="status red">RED</span> Unknown profile, MDM enrollment, root certificate, proxy, VPN, background item, automation grant, or LaunchAgent remains active.</p>
</div>
</section>
<section class="step">
<h2>9. Review Logs Around The Exposure Window</h2>
<p>Logs can support a finding. They cannot prove safety. macOS may omit or redact keychain details. Keep the window as narrow as possible; logs may contain private paths, bundle IDs, account names, hostnames, and unrelated activity.</p>
<pre><code>START="YYYY-MM-DD HH:MM:SS"
END="YYYY-MM-DD HH:MM:SS"
log show --style compact --start "$START" --end "$END" \
--predicate 'process == "securityd" OR process == "SecurityAgent" OR process == "Keychain Access" OR eventMessage CONTAINS[c] "keychain" OR eventMessage CONTAINS[c] "Always Allow" OR eventMessage CONTAINS[c] "ACL" OR eventMessage CONTAINS[c] "authorization"' \
&gt; "$EVIDENCE/keychain-window.log" 2&gt;&amp;1
/usr/bin/grep -Ei "$SUSPICIOUS" "$EVIDENCE/keychain-window.log" \
&gt; "$EVIDENCE/suspicious-keychain-log-lines.txt" 2&gt;&amp;1</code></pre>
<ul class="checklist">
<li><input type="checkbox"><span>The exposure window was set to the time period when the suspicious prompt may have happened.</span></li>
<li><input type="checkbox"><span>Any log line involving unknown apps, shells, interpreters, package managers, editors, or temp paths is treated as <span class="status red">RED</span> until explained.</span></li>
<li><input type="checkbox"><span>No log result was treated as proof that access did not happen.</span></li>
</ul>
</section>
<section class="step">
<h2>10. Revoke Local Grants, Then Rotate Upstream</h2>
<p>The local order matters. Remove persistence and local keychain trust first; then rotate provider credentials from a clean device.</p>
<ol>
<li>Dead-man LaunchAgents, editor hooks, package hooks, and suspicious autoload paths are inert.</li>
<li>Unknown keychain ACL grants are removed.</li>
<li>High-value items no longer allow all applications.</li>
<li>Adjacent macOS trust surfaces are reviewed.</li>
<li>Only then use a clean device to revoke and rotate provider credentials.</li>
</ol>
<table>
<thead><tr><th>Provider</th><th>Rotate or revoke</th></tr></thead>
<tbody>
<tr><td>GitHub</td><td>PATs, fine-grained tokens, OAuth grants, deploy keys, SSH keys, GitHub App credentials, Actions secrets.</td></tr>
<tr><td>npm</td><td>Automation tokens, publish tokens, trusted publisher bindings, organization membership, recent publishes.</td></tr>
<tr><td>Cloud</td><td>AWS, GCP, Azure, Kubernetes, Vault, Docker registries, CI/CD secrets.</td></tr>
<tr><td>Identity and apps</td><td>Email, password manager, AI tools, browsers, chat, SSO, recovery codes, passkeys where appropriate.</td></tr>
</tbody>
</table>
<div class="danger">
<p><strong>Do not confuse <code>gh auth logout</code> with provider-side revocation.</strong> Local logout removes local auth state; it does not necessarily invalidate the token upstream.</p>
</div>
</section>
<section class="step">
<h2>11. Reboot And Recheck</h2>
<p>A clean state must survive reboot. Recurrence means you missed a persistence layer.</p>
<ul class="checklist">
<li><input type="checkbox"><span>Reboot offline.</span></li>
<li><input type="checkbox"><span>Rerun the keychain inventory, ACL grant search, adjacent persistence scan, and startup hook checks into a fresh evidence folder.</span></li>
<li><input type="checkbox"><span>Compare the new outputs to the previous outputs. Any new grant, profile, background item, LaunchAgent, editor hook, package hook, or startup command is <span class="status red">RED</span>.</span></li>
<li><input type="checkbox"><span>Reopen Keychain Access and spot-check previously changed high-value items.</span></li>
<li><input type="checkbox"><span>Reconnect network only after local recurrence checks are clean and provider rotations are underway from a clean device.</span></li>
</ul>
</section>
<section class="gate">
<h2>Final Keychain Trust Status Only</h2>
<p><span class="status green">GREEN</span> Every high-value item was reviewed, unknown ACL grants were removed, broad access is absent, adjacent macOS trust surfaces are clean, credentials reachable during exposure were rotated or revoked upstream from a clean device, and reboot did not recreate trust grants or autoload paths.</p>
<p><span class="status yellow">YELLOW</span> Any keychain, ACL dump, credential family, log window, TCC setting, profile, LaunchAgent, Login Item, or high-value item was not inspected or cannot be explained.</p>
<p><span class="status red">RED</span> Any unknown app has persistent keychain access; any high-value item allows all applications; any shell, interpreter, editor, package manager, temp-path binary, or unknown helper is trusted for secrets; any suspicious profile or background item remains; or any exposed upstream credential remains valid.</p>
<p><strong>Scope limit:</strong> this status does not prove the host was never otherwise compromised. Unknown keychain scope, unknown persistence scope, or uncertainty about which prompt was granted stays yellow/red. High-assurance recovery remains erase/reinstall or restore from a known pre-exposure snapshot plus credential rotation from a clean device.</p>
</section>
<section class="note">
<h2>References</h2>
<p>Apple documents <code>Deny</code>, <code>Allow Once</code>, and <code>Always Allow</code> in Keychain Access, and says <code>Always Allow</code> lets the app retrieve that item again without prompting. Apple developer documentation describes macOS keychain item ACLs as per-item trusted-application lists. The <code>security</code> command documents <code>dump-keychain -a</code> for ACL metadata and <code>-d</code> for decrypted data.</p>
</section>
</main>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment