Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions docs/_source.html
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@
<a href="#reports">Reports</a>
<a href="#detect">Check your machine</a>
<a href="#install">Install</a>
<a href="#infected">If you're infected</a>
<a href="#commands">Commands</a>
<a href="#security">Report a bug</a>
<a class="nav-cta" href="#install">Install snare</a>
Expand Down Expand Up @@ -736,6 +737,94 @@ <h2>Pick your machine</h2>
</div>
</section>

<section class="stack" id="infected">
<span class="eyebrow">Found something?</span>
<h2>What to do, in the order that matters</h2>
<p class="lede">The instinct is to clean the repositories first. That is the wrong
order, and on an infected machine it is wasted work — this family injects into
commits on their way out, so it re-infects whatever you just cleaned.</p>

<div class="stack-s">
<ol class="chain">
<li><b>1 · Rotate your credentials</b><span>Stealing them is the objective; the
file in your repository is delivery, not payload. Removing it does not un-steal
a token. <b>npm write tokens first</b> — a stolen one lets the worm publish
trojanised versions of your other packages under your name, which is how one
laptop becomes a supply-chain incident.</span></li>
<li><b>2 · Clean the machine you push from</b><span>Before touching any
repository. If several repositories are flagged, the common factor is almost
always the machine that pushed to them, not several separate accidents.</span></li>
<li><b>3 · Then clean the repositories</b><span><code>snare fix</code> is a dry
run by default and always backs up first. Only add <code>--push</code> once you
have read what it would change.</span></li>
<li><b>4 · Then tell your collaborators</b><span>They may be infected from the
same source, and if you rewrote history their clones will break with no
explanation.</span></li>
</ol>

<h3>1 · Rotate</h3>
<div class="panel"><div class="panel-bar"><span class="label">Do this first</span><div class="bar-actions"></div></div>
<div class="scroller"><pre>snare rotate # the ordered checklist, plus a GitHub audit</pre></div></div>
<p class="note">In order: <strong>npm tokens</strong>
(<code>npmjs.com/settings/~/tokens</code> — write and "bypass 2FA" first), then
<strong>GitHub</strong> PATs, OAuth apps and SSH keys, then <strong>cloud keys</strong>
(AWS — including Secrets Manager in every region you use — GCP, Azure), then
everything else the process could read: <code>.env</code> files in any project you
had open, kubeconfig, Vault tokens, database URLs, VPN, AI-service keys, wallet
keys and seed phrases. Finally <strong>your clipboard</strong> — this family has a
documented clipboard stealer, so a password pasted out of a manager while infected
should be treated as seen.</p>

<h3>2 · Clean the machine</h3>
<div class="panel"><div class="panel-bar"><span class="label">Before touching any repo</span><div class="bar-actions"></div></div>
<div class="scroller"><pre>snare doctor # machine, persistence spots, your GitHub account
snare guard scan # one-shot process check
snare guard install # kill the loader on sight, from login onwards</pre></div></div>
<p class="note"><strong><code>guard scan</code> saying "clean" does not mean you
were never infected.</strong> It reports what is running <em>right now</em>. The
loader fires when a build runs or an editor opens the folder, does its work, and
exits. A clean process check alongside infected repositories is the expected
result, not a contradiction.</p>

<h3>3 · Clean the repositories</h3>
<div class="panel"><div class="panel-bar"><span class="label">Dry run first — always</span><div class="bar-actions"></div></div>
<div class="scroller"><pre>snare fix owner/repo # dry run, changes nothing
snare fix --all # dry run over everything flagged

snare fix owner/repo --push # clean the branch tips
snare fix owner/repo --purge-history --push # erase it from all history</pre></div></div>
<p class="note"><code>--purge-history</code> rewrites every commit, so <strong>every
SHA changes</strong>. Collaborators must delete their clone and re-clone; a
<code>git pull</code> will conflict or silently reintroduce old objects. Forks and
pull-request refs keep the old objects regardless — ask GitHub Support to
garbage-collect them if that matters to you.</p>
<p class="note">Afterwards, verify rather than assume. <code>snare scan github</code>
only reads branch tips; run <code>snare scan repo</code> against a fresh clone to
walk full history.</p>

<h3>4 · Tell the others</h3>
<div class="panel"><div class="panel-bar"><span class="label">Contacts and templates</span><div class="bar-actions"></div></div>
<div class="scroller"><pre>snare notify owner/repo # list collaborators, preview the notice
snare notify owner/repo --issue # file a GitHub issue @-mentioning them
snare notify owner/repo --mail # open a pre-filled mail draft per person</pre></div></div>
<p class="note">Both templates lead with credential rotation and say plainly that
this is not an accusation: the malware injects from an already-infected machine, so
a collaborator whose commit carried it was almost certainly a victim rather than a
cause. Say that early, or you will not get the reply you need.</p>

<h3>If a lot of repositories are flagged</h3>
<p>Fifteen infected repositories is not fifteen accidents. It points at one machine
that pushed to all of them. Work out which machine that is before cleaning anything,
or you will clean the same repositories twice.</p>
<p class="note">Worth knowing what you scaffold new projects with — a starter
template, a <code>create-*</code> command, a downloaded theme, an editor extension.
The delivery mechanism for this family is <strong>still unidentified</strong>, and
that is the single most useful thing anyone can contribute. If you find yours,
<a href="https://github.com/AviOfLagos/snare/discussions">say so in the
discussion</a>.</p>
</div>
</section>

<section class="stack" id="commands">
<span class="eyebrow">Reference</span>
<h2>Every command, and what it will not do</h2>
Expand Down
89 changes: 89 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@
<a href="#reports">Reports</a>
<a href="#detect">Check your machine</a>
<a href="#install">Install</a>
<a href="#infected">If you're infected</a>
<a href="#commands">Commands</a>
<a href="#security">Report a bug</a>
<a class="nav-cta" href="#install">Install snare</a>
Expand Down Expand Up @@ -773,6 +774,94 @@ <h2>Pick your machine</h2>
</div>
</section>

<section class="stack" id="infected">
<span class="eyebrow">Found something?</span>
<h2>What to do, in the order that matters</h2>
<p class="lede">The instinct is to clean the repositories first. That is the wrong
order, and on an infected machine it is wasted work — this family injects into
commits on their way out, so it re-infects whatever you just cleaned.</p>

<div class="stack-s">
<ol class="chain">
<li><b>1 · Rotate your credentials</b><span>Stealing them is the objective; the
file in your repository is delivery, not payload. Removing it does not un-steal
a token. <b>npm write tokens first</b> — a stolen one lets the worm publish
trojanised versions of your other packages under your name, which is how one
laptop becomes a supply-chain incident.</span></li>
<li><b>2 · Clean the machine you push from</b><span>Before touching any
repository. If several repositories are flagged, the common factor is almost
always the machine that pushed to them, not several separate accidents.</span></li>
<li><b>3 · Then clean the repositories</b><span><code>snare fix</code> is a dry
run by default and always backs up first. Only add <code>--push</code> once you
have read what it would change.</span></li>
<li><b>4 · Then tell your collaborators</b><span>They may be infected from the
same source, and if you rewrote history their clones will break with no
explanation.</span></li>
</ol>

<h3>1 · Rotate</h3>
<div class="panel"><div class="panel-bar"><span class="label">Do this first</span><div class="bar-actions"></div></div>
<div class="scroller"><pre>snare rotate # the ordered checklist, plus a GitHub audit</pre></div></div>
<p class="note">In order: <strong>npm tokens</strong>
(<code>npmjs.com/settings/~/tokens</code> — write and "bypass 2FA" first), then
<strong>GitHub</strong> PATs, OAuth apps and SSH keys, then <strong>cloud keys</strong>
(AWS — including Secrets Manager in every region you use — GCP, Azure), then
everything else the process could read: <code>.env</code> files in any project you
had open, kubeconfig, Vault tokens, database URLs, VPN, AI-service keys, wallet
keys and seed phrases. Finally <strong>your clipboard</strong> — this family has a
documented clipboard stealer, so a password pasted out of a manager while infected
should be treated as seen.</p>

<h3>2 · Clean the machine</h3>
<div class="panel"><div class="panel-bar"><span class="label">Before touching any repo</span><div class="bar-actions"></div></div>
<div class="scroller"><pre>snare doctor # machine, persistence spots, your GitHub account
snare guard scan # one-shot process check
snare guard install # kill the loader on sight, from login onwards</pre></div></div>
<p class="note"><strong><code>guard scan</code> saying "clean" does not mean you
were never infected.</strong> It reports what is running <em>right now</em>. The
loader fires when a build runs or an editor opens the folder, does its work, and
exits. A clean process check alongside infected repositories is the expected
result, not a contradiction.</p>

<h3>3 · Clean the repositories</h3>
<div class="panel"><div class="panel-bar"><span class="label">Dry run first — always</span><div class="bar-actions"></div></div>
<div class="scroller"><pre>snare fix owner/repo # dry run, changes nothing
snare fix --all # dry run over everything flagged

snare fix owner/repo --push # clean the branch tips
snare fix owner/repo --purge-history --push # erase it from all history</pre></div></div>
<p class="note"><code>--purge-history</code> rewrites every commit, so <strong>every
SHA changes</strong>. Collaborators must delete their clone and re-clone; a
<code>git pull</code> will conflict or silently reintroduce old objects. Forks and
pull-request refs keep the old objects regardless — ask GitHub Support to
garbage-collect them if that matters to you.</p>
<p class="note">Afterwards, verify rather than assume. <code>snare scan github</code>
only reads branch tips; run <code>snare scan repo</code> against a fresh clone to
walk full history.</p>

<h3>4 · Tell the others</h3>
<div class="panel"><div class="panel-bar"><span class="label">Contacts and templates</span><div class="bar-actions"></div></div>
<div class="scroller"><pre>snare notify owner/repo # list collaborators, preview the notice
snare notify owner/repo --issue # file a GitHub issue @-mentioning them
snare notify owner/repo --mail # open a pre-filled mail draft per person</pre></div></div>
<p class="note">Both templates lead with credential rotation and say plainly that
this is not an accusation: the malware injects from an already-infected machine, so
a collaborator whose commit carried it was almost certainly a victim rather than a
cause. Say that early, or you will not get the reply you need.</p>

<h3>If a lot of repositories are flagged</h3>
<p>Fifteen infected repositories is not fifteen accidents. It points at one machine
that pushed to all of them. Work out which machine that is before cleaning anything,
or you will clean the same repositories twice.</p>
<p class="note">Worth knowing what you scaffold new projects with — a starter
template, a <code>create-*</code> command, a downloaded theme, an editor extension.
The delivery mechanism for this family is <strong>still unidentified</strong>, and
that is the single most useful thing anyone can contribute. If you find yours,
<a href="https://github.com/AviOfLagos/snare/discussions">say so in the
discussion</a>.</p>
</div>
</section>

<section class="stack" id="commands">
<span class="eyebrow">Reference</span>
<h2>Every command, and what it will not do</h2>
Expand Down
68 changes: 58 additions & 10 deletions lib/guard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -214,20 +214,68 @@ UNIT

# ---- Windows (Git Bash): Scheduled Task ----------------------------------
guard_svc_schtasks(){
local name="snare-guard" bash_exe
local name="snare-guard" bash_exe snare_sh err rc
bash_exe="$(command -v bash)"
snare_sh="$SNARE_ROOT/bin/snare"

# schtasks.exe is a Windows program: it needs a Windows path for the
# executable. Handing it an MSYS path like /usr/bin/bash fails, and the old
# code discarded stderr so the user only ever saw "could not create the
# scheduled task" with nothing to act on.
if command -v cygpath >/dev/null 2>&1; then
bash_exe="$(cygpath -w "$bash_exe" 2>/dev/null || command -v bash)"
fi
# MSYS_NO_PATHCONV stops MSYS rewriting /TN and friends into paths.
export MSYS_NO_PATHCONV=1

case "$1" in
install)
schtasks //Create //TN "$name" //SC ONLOGON //RL LIMITED //F \
//TR "\"$bash_exe\" -lc 'snare guard run --interval 1'" >/dev/null 2>&1 \
&& grn "scheduled task '$name' created (runs at logon)" || red "could not create the scheduled task"
schtasks //Run //TN "$name" >/dev/null 2>&1 && grn "started" ;;
uninstall) schtasks //Delete //TN "$name" //F >/dev/null 2>&1 && ylw "guard uninstalled" ;;
start) schtasks //Run //TN "$name" >/dev/null 2>&1 && grn "started" ;;
stop) schtasks //End //TN "$name" >/dev/null 2>&1 && ylw "stopped" ;;
err="$(schtasks /Create /TN "$name" /SC ONLOGON /RL LIMITED /F \
/TR "\"$bash_exe\" -c \"'$snare_sh' guard run --interval 1\"" 2>&1)"; rc=$?
if [ "$rc" = 0 ]; then
grn "scheduled task '$name' created (runs at logon)"
schtasks /Run /TN "$name" >/dev/null 2>&1 && grn "started"
return 0
fi
red "could not create the scheduled task"
[ -n "$err" ] && echo "$err" | sed 's/^/ /'
echo
# A Startup-folder shortcut needs no privileges and no Task Scheduler.
ylw " Falling back to a Startup entry (no admin rights needed)."
local startup
startup="$(cygpath -u "$APPDATA" 2>/dev/null)/Microsoft/Windows/Start Menu/Programs/Startup"
if [ -d "$startup" ]; then
cat > "$startup/snare-guard.bat" <<BAT
@echo off
start "" /min "$bash_exe" -c "'$snare_sh' guard run --interval 1"
BAT
grn " wrote $startup/snare-guard.bat"
dim " it will start at your next logon; to start it now:"
dim " snare guard run --interval 1 &"
return 0
fi
red " could not find your Startup folder either."
dim " Run the guard manually in a spare Git Bash window:"
dim " snare guard run --interval 1"
dim " Or scan on demand: snare guard scan"
return 1 ;;
uninstall)
schtasks /Delete /TN "$name" /F >/dev/null 2>&1 && ylw "guard uninstalled"
local startup
startup="$(cygpath -u "$APPDATA" 2>/dev/null)/Microsoft/Windows/Start Menu/Programs/Startup"
[ -f "$startup/snare-guard.bat" ] && { rm -f "$startup/snare-guard.bat"; ylw "startup entry removed"; }
return 0 ;;
start) schtasks /Run /TN "$name" >/dev/null 2>&1 && grn "started" || \
dim "start it manually: snare guard run --interval 1 &" ;;
stop) schtasks /End /TN "$name" >/dev/null 2>&1 && ylw "stopped" ;;
status)
if schtasks //Query //TN "$name" >/dev/null 2>&1; then grn "scheduled task '$name' registered"
else ylw "guard not registered — 'snare guard install'"; fi
if schtasks /Query /TN "$name" >/dev/null 2>&1; then grn "scheduled task '$name' registered"
else
local startup
startup="$(cygpath -u "$APPDATA" 2>/dev/null)/Microsoft/Windows/Start Menu/Programs/Startup"
if [ -f "$startup/snare-guard.bat" ]; then grn "startup entry registered"
else ylw "guard not registered — 'snare guard install'"; fi
fi
_guard_common_status ;;
esac
}
Loading
Loading