<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on Lukas Manera</title><link>https://blog.xarc.dev/posts/</link><description>Recent content in Posts on Lukas Manera</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>&lt;a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" rel="noopener"&gt;CC BY-NC 4.0&lt;/a&gt;</copyright><lastBuildDate>Mon, 06 Apr 2026 12:00:00 +0200</lastBuildDate><atom:link href="https://blog.xarc.dev/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Playbook for Hardening Legacy PHP</title><link>https://blog.xarc.dev/posts/2026/04/playbook-for-hardening-legacy-php/</link><pubDate>Mon, 06 Apr 2026 12:00:00 +0200</pubDate><guid>https://blog.xarc.dev/posts/2026/04/playbook-for-hardening-legacy-php/</guid><description>&lt;p&gt;This is my practical follow-up to my post on &lt;a href="https://blog.xarc.dev/posts/2026/04/hardening-legacy-php-in-constrained-environments/"&gt;threat modeling legacy PHP in constrained environments&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That post is more about mindset, prioritization, and how to think about risk when the system is messy but the business relies on it.&lt;/p&gt;
&lt;p&gt;This one is the hands-on version. It is the kind of outline I come back to at the start of a new project where the codebase is fragile, the DevOps story is rudimentary at best, and nobody is getting six months to clean things up before security work starts.&lt;/p&gt;</description><content type="html"><![CDATA[<p>This is my practical follow-up to my post on <a href="/posts/2026/04/hardening-legacy-php-in-constrained-environments/">threat modeling legacy PHP in constrained environments</a>.</p>
<p>That post is more about mindset, prioritization, and how to think about risk when the system is messy but the business relies on it.</p>
<p>This one is the hands-on version. It is the kind of outline I come back to at the start of a new project where the codebase is fragile, the DevOps story is rudimentary at best, and nobody is getting six months to clean things up before security work starts.</p>
<p>In smaller teams, the goal is usually to move fast, fix what is already known to be broken, and work out what actually needs attention first.</p>
<hr>
<h2 id="my-first-assessment">My first assessment</h2>
<p>So my first pass on a legacy PHP system is usually an inventory pass.</p>
<p>Some of this can be assessed directly from the codebase, configuration, and host. Some of it I need to ask the team about, because ownership, deployment, and backup or recovery mechanisms usually do not live in the application itself.</p>
<p>I want a rough map of:</p>
<ul>
<li>PHP version</li>
<li>PHP runtime details, including web vs CLI version, loaded extensions, and configuration differences</li>
<li>web server type and version</li>
<li>application, framework, or CMS version</li>
<li>Composer packages and their versions</li>
<li>database engine and version</li>
<li>public entry points</li>
<li>admin routes and privileged functionality</li>
<li>scheduled jobs</li>
<li>writable directories</li>
<li>file upload paths</li>
<li>outbound email setup</li>
<li>external integrations, callback endpoints, and trust boundaries</li>
<li>session storage behavior and where sessions actually live</li>
<li>available logs and how to access them</li>
<li>backup and restore mechanism</li>
<li>TLS and certificate setup</li>
<li>reverse proxy or load balancer behavior if the app sits behind one</li>
<li>deployment method and rollback path</li>
<li>where configuration and secrets actually live</li>
<li>whether there is a staging or test environment and what state it is in</li>
<li>which checks exist before a change goes live</li>
<li>who actually owns deployment, credentials, and alerts</li>
<li>where documentation is located, if there is any</li>
</ul>
<p>This is basic, but it already tells me a lot about the state of things. Often nobody has the whole picture anymore. The people who used to know it may not even be there.</p>
<p>I am not looking for perfect documentation here. I just want to avoid assuming too much of what would be considered fundamental in a modern environment. Without that, it is easy to spend time assessing the wrong layer of risk.</p>
<p>I have seen environments with long conversations about “future architecture” while a backup archive sat under the web root and a forgotten admin script had no authentication. I have also seen log files grow for years without rotation or monitoring, gaps in version control, and old libraries nobody knew were still in use.</p>
<hr>
<h2 id="what-i-usually-prioritize-first">What I usually prioritize first</h2>
<p>The exact order changes, but the pattern is pretty stable.</p>
<h3 id="1-reduce-exposed-surface-area">1. Reduce exposed surface area</h3>
<p>Before adding anything fancy, I want less attack surface.</p>
<p>In the first week, simple removals and cleanup often buy the most risk reduction.</p>
<p>That often means:</p>
<ul>
<li>removing forgotten scripts and backups from web-accessible paths</li>
<li>disabling debug routes and test endpoints</li>
<li>restricting admin panels by IP where possible</li>
<li>moving dangerous maintenance utilities out of public reach</li>
<li>reviewing which directories are writable by the application</li>
</ul>
<p>After enough years, convenience tends to produce the biggest security liabilities.</p>
<h3 id="2-fix-the-easy-high-impact-authentication-issues">2. Fix the easy, high-impact authentication issues</h3>
<p>If authentication is weak, everything behind it is weak.</p>
<p>On older systems, auth may be split across different parts of the application, and there may be many more entry points than anyone would design today.</p>
<p>Things I usually look at early:</p>
<ul>
<li>admin panel exposure</li>
<li>password reset behavior</li>
<li>session fixation and session regeneration</li>
<li>shared accounts</li>
<li>weak role boundaries</li>
<li>default, weak, or rarely rotated credentials</li>
</ul>
<p>Even if you cannot redo the identity layer, you can often still reduce the number of entry points, make them share the same authentication logic, add IP restrictions around admin areas, and tighten credential rules.</p>
<h3 id="3-get-dependency-and-release-visibility-under-control">3. Get dependency and release visibility under control</h3>
<p>On older PHP systems, dependencies are often less obvious than they should be. Composer was not part of every PHP workflow for a long time, especially before 2015, and libraries may simply have been unpacked and included manually.</p>
<p>I want to know:</p>
<ul>
<li>which packages are installed</li>
<li>which are abandoned</li>
<li>which are pinned to very old versions</li>
<li>which are actually used</li>
<li>whether the application depends on unsupported framework versions</li>
<li>whether production was built from Composer, copied by hand, or assembled in some other creative way</li>
</ul>
<p>Dependency blindness is not acceptable. If you cannot answer what is installed and how it gets to production, you are going to miss easily avoidable security issues.</p>
<h3 id="4-make-degradation-easier-to-notice">4. Make degradation easier to notice</h3>
<p>Detection matters even more in constrained environments because prevention is never perfect and there is usually no dedicated monitoring or security team waiting nearby.</p>
<p>I want visibility into:</p>
<ul>
<li>site reachability</li>
<li>unusual HTTP failures</li>
<li>recent application errors</li>
<li>disk usage</li>
<li>CPU and memory pressure</li>
<li>log spikes</li>
<li>job failures</li>
<li>session anomalies</li>
<li>certificate expiry</li>
<li>outbound mail oddities</li>
</ul>
<p>This is one reason I built tools like <a href="/posts/2025/06/mata-monitoring-legacy-php-applications/">MATA</a>: in many of these environments, full observability stacks were unrealistic, but a simple monitoring endpoint still goes a long way.</p>
<h3 id="5-verify-backups">5. Verify backups</h3>
<p>A backup that has never been restored is not enough.</p>
<p>For legacy apps, backup review is part of hardening. I want to know whether a rollback is actually possible and who can perform it, without relying on one person remembering a manual process from two years ago.</p>
<h3 id="6-review-delivery-and-deployment">6. Review delivery and deployment</h3>
<p>I am not looking for perfect platform engineering here. But if security is treated as a one-time fix instead of a continuous effort, we are missing the point.</p>
<p>That usually means:</p>
<ul>
<li>moving away from ad hoc file uploads toward a basic repeatable deployment path</li>
<li>documenting where configuration and secrets live, and keeping them out of version control</li>
<li>making sure code, config, and backup changes have an owner</li>
<li>adding at least one cheap pre-release check or smoke test</li>
</ul>
<p>This does not need to be fancy. It just needs to be reliable enough to not stand in the way of security updates.</p>
<hr>
<h2 id="if-i-only-get-one-week">If I only get one week</h2>
<p>The schedule depends on access and team availability, but if I only get a short window, the first week would usually look something like this:</p>
<h3 id="day-1-2-build-the-map">Day 1-2: build the map</h3>
<ul>
<li>identify the app, framework, PHP version, and major dependencies</li>
<li>map public entry points, admin routes, upload paths, and writable directories</li>
<li>find scheduled jobs, backup jobs, and mail-sending paths</li>
<li>identify who owns deployment, credentials, DNS/TLS, and receives alerts</li>
<li>confirm how logs are accessed and whether there is any staging environment at all</li>
</ul>
<p>The goal here is orientation and figuring out who to talk to.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># file operations and upload-related behavior</span>
</span></span><span style="display:flex;"><span>rg -n --glob <span style="color:#e6db74">&#39;*.php&#39;</span> <span style="color:#e6db74">&#39;(fopen|file_get_contents|unlink|move_uploaded_file|\$_FILES)&#39;</span>
</span></span></code></pre></div><h3 id="day-3-remove-obvious-exposure">Day 3: remove obvious exposure</h3>
<ul>
<li>remove or block forgotten scripts, backups, and test files under the web root</li>
<li>disable debug functionality in production</li>
<li>restrict admin panels and maintenance utilities</li>
<li>review the most dangerous writable paths</li>
<li>check for <code>phpinfo()</code> and similar footguns</li>
</ul>
<p>This is often the easiest place to reduce risk quickly.</p>
<h3 id="day-4-review-auth-dependencies-and-secrets">Day 4: review auth, dependencies, and secrets</h3>
<ul>
<li>review the auth layer: login, password reset, and session handling</li>
<li>find where secrets and configuration are stored</li>
<li>create a dependency inventory and flag unsupported or abandoned components</li>
<li>note how code and dependencies actually get into production</li>
</ul>
<p>This usually shows whether the application risk is mostly code-level, operational, or both.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># include / require hotspots</span>
</span></span><span style="display:flex;"><span>rg -n --glob <span style="color:#e6db74">&#39;*.php&#39;</span> <span style="color:#e6db74">&#39;(include|require)(_once)?\s*\(&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># session initialization</span>
</span></span><span style="display:flex;"><span>rg -n --glob <span style="color:#e6db74">&#39;*.php&#39;</span> <span style="color:#e6db74">&#39;session_start\s*\(&#39;</span>
</span></span></code></pre></div><h3 id="day-5-add-visibility-and-verify-recovery">Day 5: add visibility and verify recovery</h3>
<ul>
<li>review application error logs and recurring job failures</li>
<li>confirm backups, retention, and who can perform a restore</li>
<li>walk through a restore or rollback path on paper</li>
<li>write down the highest-priority next actions</li>
</ul>
<p>Week one should produce a usable starting point for week two.</p>
<h2 id="what-i-want-at-the-end-of-week-one">What I want at the end of week one</h2>
<p>If the first week went reasonably well, a short report should allow somebody else to pick things up without starting from zero.</p>
<p>Usually that means:</p>
<ul>
<li>a dependency snapshot</li>
<li>a list of public entry points and admin routes worth reviewing</li>
<li>a list of writable directories and upload paths</li>
<li>named owners for deployment, backups, TLS, and alerts</li>
<li>a shortlist of immediate fixes</li>
<li>a shortlist of follow-up automation tasks</li>
<li>a rough note on what looks brittle</li>
</ul>
<p>That is enough to drive the next round of work.</p>
<p>That next round often includes reviewing obvious data-flow and query risks such as direct use of request input, unsafe SQL construction, weak validation, and risky file handling.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># request input hotspots</span>
</span></span><span style="display:flex;"><span>rg -n --glob <span style="color:#e6db74">&#39;*.php&#39;</span> <span style="color:#e6db74">&#39;\$_(GET|POST|REQUEST|COOKIE|FILES)&#39;</span>
</span></span></code></pre></div><h2 id="tracepack">tracepack</h2>
<p>I built <a href="/showcase/tracepack/"><strong>tracepack</strong></a>, a small Go CLI for quickly scanning codebases with YAML profiles and saving the results as Markdown.</p>
<p>For this kind of legacy PHP assessment, that is useful in two ways:</p>
<ul>
<li><strong>footprint</strong> gives a compact overview of the codebase</li>
<li><strong>summary</strong> runs reusable searches or commands and saves the output as a review bundle</li>
</ul>
<p>The bundled default profile is <code>php-legacy</code>, so it is handy for quickly collecting things like request input hotspots, session handling, include and require relationships, file operations, and likely config or secret locations.</p>
<p>It is intentionally lightweight rather than a full static analyzer. The value is fast orientation, repeatable searches, and artifacts that are easy to review or share.</p>
<p>Repository: <a href="https://github.com/xarcdotdev/xarc-tracepack">github.com/xarcdotdev/xarc-tracepack</a></p>
<hr>
<h2 id="useful-low-friction-automation">Useful low-friction automation</h2>
<p>In these environments, small automation that reduces blind spots is usually more valuable than ambitious automation that nobody maintains.</p>
<p>Useful examples:</p>
<ul>
<li>
<p><strong>nightly dependency inventory export</strong><br>
Record dependency versions somewhere predictable so changes and vulnerable packages are easier to spot.</p>
</li>
<li>
<p><strong>basic web root change detection</strong><br>
A checksum, file listing diff, or simple integrity check is often enough to notice unexpected changes in publicly served directories.</p>
</li>
<li>
<p><strong>certificate expiry alerts</strong><br>
Cheap, boring, and absolutely worth it wherever it makes sense.</p>
</li>
<li>
<p><strong>disk pressure and job failure alerts</strong><br>
Many incidents people first describe as “security problems” are really failures of robustness, visibility, and system hygiene.</p>
</li>
<li>
<p><strong>scheduled smoke tests for critical paths</strong><br>
A login path, admin path, checkout flow, or key API endpoint tested on a schedule can catch breakage early.</p>
</li>
<li>
<p><strong>mail volume or anomaly checks</strong><br>
Especially useful where old apps can be abused for spam or phishing and nobody notices until reputation damage shows up.</p>
</li>
<li>
<p><strong>backup job success or failure notification</strong><br>
It is better to know that last night’s backup failed before you need it.</p>
</li>
<li>
<p><strong>a minimal pre-release gate</strong><br>
Even one or two checks before deployment — for example, <code>composer audit</code>, a linter, or a smoke test — can keep easy mistakes out of production.</p>
</li>
</ul>
<p>This is obviously not a full DevSecOps platform. But simple guardrails that are cheap and easy to keep running are usually the better fit here.</p>
<p>A cron job as simple as running <code>composer audit</code> can already improve visibility.</p>
<p>A minimal Bash example could look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env bash
</span></span></span><span style="display:flex;"><span>set -u
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cd /var/www/myapp <span style="color:#f92672">||</span> exit <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> ! command -v composer &gt;/dev/null 2&gt;&amp;1; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>  exit <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> ! output<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>composer audit --no-interaction 2&gt;&amp;1<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    printf <span style="color:#e6db74">&#39;To: ops@example.com\n&#39;</span>
</span></span><span style="display:flex;"><span>    printf <span style="color:#e6db74">&#39;Subject: [legacy-php] composer audit findings on %s\n&#39;</span> <span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>hostname<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    printf <span style="color:#e6db74">&#39;\n&#39;</span>
</span></span><span style="display:flex;"><span>    printf <span style="color:#e6db74">&#39;Directory: %s\n\n&#39;</span> <span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>pwd<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    printf <span style="color:#e6db74">&#39;%s\n&#39;</span> <span style="color:#e6db74">&#34;</span>$output<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">}</span> | /usr/sbin/sendmail -t
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span></code></pre></div><p>If needed, this can easily be scaled across multiple applications by wrapping the same idea around a small loop over known project directories or <code>composer.lock</code> files.</p>
<hr>
<h2 id="a-practical-hardening-checklist">A practical hardening checklist</h2>
<p>This is the kind of checklist I find useful for real-world legacy PHP systems.</p>
<h3 id="application">Application</h3>
<ul>
<li><input disabled="" type="checkbox"> Identify framework, CMS, or app version</li>
<li><input disabled="" type="checkbox"> Inventory Composer dependencies</li>
<li><input disabled="" type="checkbox"> Remove unused packages and plugins</li>
<li><input disabled="" type="checkbox"> Compare web and CLI PHP versions, extensions, and relevant <code>php.ini</code> settings</li>
<li><input disabled="" type="checkbox"> Identify public entry points and admin routes</li>
<li><input disabled="" type="checkbox"> Disable debug mode in production</li>
<li><input disabled="" type="checkbox"> Search for forgotten scripts, test files, <code>phpinfo()</code> pages, and backups in the web root</li>
<li><input disabled="" type="checkbox"> Review file upload handling and executable upload risk</li>
<li><input disabled="" type="checkbox"> Review password reset, session, and cookie behavior</li>
<li><input disabled="" type="checkbox"> Review obvious data-flow and query risks around request input and SQL construction</li>
<li><input disabled="" type="checkbox"> Verify access control around admin and privileged functionality</li>
</ul>
<h3 id="host-and-deployment">Host and deployment</h3>
<ul>
<li><input disabled="" type="checkbox"> Review writable directories and permissions</li>
<li><input disabled="" type="checkbox"> Verify TLS is current and auto-renewal works</li>
<li><input disabled="" type="checkbox"> Identify cron jobs and scheduled scripts</li>
<li><input disabled="" type="checkbox"> Document deployment method and rollback path</li>
<li><input disabled="" type="checkbox"> Document where configuration and secrets live</li>
<li><input disabled="" type="checkbox"> Review application database privileges and reduce them where possible</li>
<li><input disabled="" type="checkbox"> Confirm deployment ownership, credential ownership, and alert ownership</li>
<li><input disabled="" type="checkbox"> Confirm secrets are not stored carelessly in public or shared locations</li>
<li><input disabled="" type="checkbox"> Review whether old releases or archives remain web-accessible</li>
<li><input disabled="" type="checkbox"> Note whether staging or test exists and document its limitations</li>
<li><input disabled="" type="checkbox"> Add at least one repeatable pre-release check or smoke test</li>
</ul>
<h3 id="monitoring-and-detection">Monitoring and detection</h3>
<ul>
<li><input disabled="" type="checkbox"> Ensure application and server logs are accessible without ad hoc manual downloading</li>
<li><input disabled="" type="checkbox"> Alert on HTTP downtime and repeated failures</li>
<li><input disabled="" type="checkbox"> Alert on disk pressure</li>
<li><input disabled="" type="checkbox"> Track certificate expiry</li>
<li><input disabled="" type="checkbox"> Review application error logs regularly</li>
<li><input disabled="" type="checkbox"> Track recent log spikes or new recurring errors</li>
<li><input disabled="" type="checkbox"> Record dependency versions for change detection</li>
<li><input disabled="" type="checkbox"> Alert on unexpected web root or dependency changes</li>
<li><input disabled="" type="checkbox"> Monitor scheduled job failures</li>
<li><input disabled="" type="checkbox"> Review outbound mail behavior for abuse indicators</li>
</ul>
<h3 id="recovery">Recovery</h3>
<ul>
<li><input disabled="" type="checkbox"> Confirm backups exist</li>
<li><input disabled="" type="checkbox"> Confirm what is included in backups</li>
<li><input disabled="" type="checkbox"> Confirm retention period</li>
<li><input disabled="" type="checkbox"> Test a restore path</li>
<li><input disabled="" type="checkbox"> Document who can perform recovery and where credentials live</li>
<li><input disabled="" type="checkbox"> Document a rollback path for application code and configuration</li>
<li><input disabled="" type="checkbox"> Make sure recovery does not depend on one person’s memory</li>
</ul>
<h3 id="process">Process</h3>
<ul>
<li><input disabled="" type="checkbox"> Decide which vulnerabilities or incidents trigger immediate action</li>
<li><input disabled="" type="checkbox"> Define who gets alerted and how</li>
<li><input disabled="" type="checkbox"> Define who owns releases, alerts, and incident response</li>
<li><input disabled="" type="checkbox"> Keep a minimal incident checklist</li>
<li><input disabled="" type="checkbox"> Record known deployment constraints and staging gaps</li>
<li><input disabled="" type="checkbox"> Document known exceptions so future reviews stay realistic</li>
</ul>
]]></content></item><item><title>Hardening Legacy PHP in Constrained Environments</title><link>https://blog.xarc.dev/posts/2026/04/hardening-legacy-php-in-constrained-environments/</link><pubDate>Sun, 05 Apr 2026 12:00:00 +0200</pubDate><guid>https://blog.xarc.dev/posts/2026/04/hardening-legacy-php-in-constrained-environments/</guid><description>&lt;p&gt;The moment you realize that the roughest codebase you’ve seen is also one of the most valuable systems you’ve touched, things start to look a little different.&lt;/p&gt;
&lt;p&gt;“Just modernize it” is not a security strategy if the main thing that matters is keeping core business processes running in a system that drives major revenue.&lt;/p&gt;
&lt;p&gt;If you get called into an old PHP application, it can feel a bit like arriving at a crash site. After the initial shock, instead of judging, you start to think like an emergency responder: assess the scene, stabilize what matters most, and reduce the risk without making the situation worse.&lt;/p&gt;</description><content type="html"><![CDATA[<p>The moment you realize that the roughest codebase you’ve seen is also one of the most valuable systems you’ve touched, things start to look a little different.</p>
<p>“Just modernize it” is not a security strategy if the main thing that matters is keeping core business processes running in a system that drives major revenue.</p>
<p>If you get called into an old PHP application, it can feel a bit like arriving at a crash site. After the initial shock, instead of judging, you start to think like an emergency responder: assess the scene, stabilize what matters most, and reduce the risk without making the situation worse.</p>
<p><img src="/img/posts/php-meme.jpg" alt="PHP Meme Image"></p>
<p>But usually, you do not get to pause the business, rebuild the stack, and come back in six months with clean infrastructure and a fresh deployment model. You have to reduce risk with the system you actually have, and you have to do it fast.</p>
<p>PHP is still the workhorse of the internet. The numbers reflect that. As of April 2026, W3Techs reports that <strong>PHP is used by 71.7% of all websites whose server-side language is known</strong>, and <strong>WooCommerce powers 49.6% of the e-commerce systems in its surveys</strong>. Exact revenue running through PHP is hard to measure from the outside, but it is obviously not a niche runtime surviving on hobby projects.</p>
<p>These systems process orders, send invoices, run customer portals, support internal operations, and keep businesses alive. They may look unimpressive from an architecture point of view, but they still matter commercially.</p>
<p>Maybe there is already a new solution in the works that is supposed to replace the application at hand. But maybe stakeholders have been through failed modernization attempts elsewhere before and do not trust timelines anymore. Or maybe there is nothing else planned, and rewriting a system that still generates major business value is simply out of the question.</p>
<p>Maybe some recent pentest results blew a few minds and raised compliance concerns.</p>
<p>The job is to improve, stabilize, and fix things up. Timeline? Yesterday. So the pressure is on from day one.</p>
<hr>
<h2 id="first-define-the-environment-honestly">First: Define the environment honestly</h2>
<p>A legacy PHP app on constrained infrastructure can mean many things, but usually we are talking about one or more of these, and often all of them:</p>
<ul>
<li>an application with direct business impact</li>
<li>maintained by a small team or even a single developer</li>
<li>built in the early 2000s to 2010s</li>
<li>deployed into an environment that it outgrew at some point</li>
<li>difficult to patch quickly without fear of breakage</li>
<li>almost no appetite for major rewrites</li>
</ul>
<p>A lot of these environments also come with very limited control and very limited resources:</p>
<ul>
<li>no root access</li>
<li>no Docker or orchestration</li>
<li>no ability to install anything on the system level</li>
<li>no consistent or properly separated staging environment</li>
<li>limited or missing centralized logs</li>
<li>shared mail setup</li>
<li>old cron jobs nobody wants to touch</li>
<li>file permissions that grew organically over the years</li>
<li>no control over subdomains or domain configuration</li>
</ul>
<p>In those environments, threat modeling has to become more pragmatic.</p>
<p>The question is not: <strong>how would we build this securely, performantly, and maintainably?</strong></p>
<p>The question is: <strong>given the system we actually have, what is most likely to go wrong next, what would have the biggest business impact, and how can we buy the most risk reduction without risking operations?</strong></p>
<p>This sounds obvious, but “without risking operations” is easier said than done.</p>
<p>Just imagine a large PHP application written mostly in procedural code, with many entry points, years of accumulated integrations, and modules that gradually took on routing and orchestration responsibilities of their own. Nobody is completely sure what is still in use, by whom. Scheduled jobs manipulate the database, import and export data, and send mail, often all of it at once in a massive PHP file that grew over many years without much opportunity for cleanup. The surrounding server setup is locked down in all the places you would like control, but at the same time feels exposed to the public.</p>
<p>It is like working on a major highway bridge that cannot be closed, even though time, load, and years of improvised repairs have left it in a bad state.</p>
<p>Now is certainly not the moment to look for guidance in <em>Clean Code</em>. It is still useful to know what good looks like when refactoring, but right now that is not the priority.</p>
<hr>
<h2 id="second-set-up-some-basic-devops-and-tools">Second: Set up some basic DevOps and tools</h2>
<p>Before thinking about controls, I try to remove unnecessary friction from the development process.</p>
<p>Common examples:</p>
<ul>
<li>fragile deployment processes: direct file uploads into production</li>
<li>chaotic dependencies: libraries copied manually into whichever directory needs them</li>
<li>little observability: logs written inconsistently across the application and host</li>
<li>version control is missing or incomplete, so deployment means manual reconciliation between environments</li>
</ul>
<p>Quickest improvements:</p>
<ul>
<li>if the test environment is unreliable, back it up somewhere and start fresh</li>
<li>ditch the FileZilla workflow and move to a basic Git clone, pull, and push process</li>
<li>introduce Composer where possible</li>
<li>use SSH and <code>tail -f</code> on actual log files instead of downloading logs by hand</li>
</ul>
<p>We do not need a perfect containerized platform first. But we do need some stability in the workflow for security improvements to stick.</p>
<hr>
<h2 id="third-pragmatic-threat-modeling">Third: Pragmatic threat modeling</h2>
<ol>
<li><strong>business priorities</strong></li>
<li><strong>internet exposure</strong></li>
<li><strong>operational weakness</strong></li>
<li><strong>application weakness</strong></li>
<li><strong>business impact</strong></li>
</ol>
<h3 id="1-business-priorities">1. Business priorities</h3>
<p>This is what matters most.</p>
<p>A complex PHP monolith is like a card house. One seemingly non-critical cron job breaks, and suddenly ordering no longer works because a tracking table was renamed non-atomically and never recovered. A third-party API goes down for a moment, and suddenly product search is gone.</p>
<p>Regardless, we need to identify the most important business logic and processes. Over the years, this is usually where the most duct tape accumulated, because whenever something broke here, phones started burning. So the company might now consider these areas “stable.” From a security perspective, though, this is often exactly where some of the worst offenders hide, and it is a good place to start auditing.</p>
<h3 id="2-audit-internet-exposure">2. Audit: Internet exposure</h3>
<p>What can an attacker reach directly?</p>
<p>Usually that includes some combination of:</p>
<ul>
<li>public HTTP endpoints</li>
<li>admin panels</li>
<li>file upload functionality</li>
<li>login and password reset flows</li>
<li>webhook endpoints</li>
<li>mail submission paths</li>
<li>outdated libraries exposed by the application</li>
</ul>
<p>For older PHP systems, this layer matters because the application surface is often larger than the team remembers. Old utility scripts, backup files, forgotten admin routes, weakly protected staging copies, and writable directories tend to accumulate over time.</p>
<blockquote>
<p>Quick tip: search for <code>phpinfo()</code> and be prepared to find surprises.</p>
</blockquote>
<h3 id="3-audit-operational-weakness">3. Audit: Operational weakness</h3>
<p>Looking only for vulnerable code misses a large chunk of the risk. A lot of it lives in operational fragility, and that is what falls on our feet right after we make any hardening changes:</p>
<ul>
<li>no good backups</li>
<li>no restore testing</li>
<li>disk fills up silently</li>
<li>certificate renewal is vague or manual</li>
<li>logs are noisy and nobody monitors them</li>
<li>alerts do not exist, or people are trained to ignore them</li>
<li>secrets are copied around manually</li>
<li>dependency versions drift between systems</li>
</ul>
<h3 id="4-audit-application-weakness">4. Audit: Application weakness</h3>
<p>I would start with things that are easy to check:</p>
<ul>
<li>unpatched CMS or framework components</li>
<li>unsafe file handling</li>
<li>stale Composer dependencies</li>
<li>debug functionality still reachable in production</li>
</ul>
<p>And then look for the obvious classics:</p>
<ul>
<li>weak input validation</li>
<li>missing prepared statements, especially around user input</li>
<li>sensitive or critical data transmitted carelessly via GET or POST</li>
<li>insecure deserialization or dynamic inclusion</li>
<li>missing CSRF tokens</li>
<li>no rate limiting</li>
<li>missing HTTP security headers</li>
<li>missing alerts for edge cases</li>
<li>weak logging and error handling</li>
<li>brittle auth or session handling</li>
<li>permission logic that drifted over the years</li>
</ul>
<h3 id="5-business-impact">5. Business impact</h3>
<p>After collecting findings, assign severity based on potential blast radius:</p>
<ul>
<li>Can this lead to account takeover?</li>
<li>Can this expose customer data?</li>
<li>Can this be turned into outbound spam or phishing?</li>
<li>Can this disrupt revenue or operations?</li>
<li>Would we notice quickly if it happened?</li>
<li>How expensive would recovery be?</li>
</ul>
<hr>
<h2 id="prioritization">Prioritization</h2>
<p>At this point, major rewrites are usually discussed, or we accept that we are fixing what we can right now and therefore have to pick carefully.</p>
<p>If I need to choose quickly, I usually prioritize issues in roughly this order:</p>
<ol>
<li><strong>Anything enabling account takeover or privileged access</strong></li>
<li><strong>Anything exposing sensitive data</strong></li>
<li><strong>Anything enabling code execution, file write abuse, or mail abuse</strong></li>
<li><strong>Anything that makes compromise hard to detect</strong></li>
<li><strong>Anything that makes recovery slow or uncertain</strong></li>
<li><strong>Everything else that improves general hygiene</strong></li>
</ol>
<p>That ordering is intentionally boring.</p>
<p>It favors practical damage reduction over neatness.</p>
<hr>
<h2 id="closing">Closing</h2>
<p>There is a temptation to look at old PHP systems and postpone serious security work until some future rewrite becomes possible. But in many cases, that exact thinking is part of why the environment stayed constrained and the application accumulated so much complexity in the first place.</p>
<p>As engineers, we want maintainability, modern practices, and clean code. Fixing and patching systems that lack basic fundamentals feels inefficient, and in the long run it can feel like a battle that is impossible to win.</p>
<p>But in reality, the business case often points the other way. It is usually much more acceptable to incrementally improve a battle-tested system that became the golden goose of a slow-moving industry. The 20-year-old PHP application may be tightly interconnected with a black-box ERP, ancient SAP systems, maybe even some RPG-based warehouse logic, and it evolved specifically to support very specific workflows in a messy ecosystem.</p>
<p>Useful matters a lot more than elegant.</p>
<p>And migrating an entire ecosystem into uncertainty introduces its own risk.</p>
<p>The good news is that there are usually plenty of quick wins.</p>
<p>For a more practical version of that process, see <a href="/posts/2026/04/playbook-for-hardening-legacy-php/">Playbook for Hardening Legacy PHP</a>.</p>
]]></content></item><item><title>MATA: Monitoring Legacy PHP Applications</title><link>https://blog.xarc.dev/posts/2025/06/mata-monitoring-legacy-php-applications/</link><pubDate>Tue, 10 Jun 2025 10:00:00 +0200</pubDate><guid>https://blog.xarc.dev/posts/2025/06/mata-monitoring-legacy-php-applications/</guid><description>&lt;p&gt;Most monitoring platforms assume you control the environment.&lt;/p&gt;
&lt;p&gt;They assume you can install agents, open ports, run background services, provision a database, and standardize deployment across every machine you touch.&lt;/p&gt;
&lt;p&gt;That is not the reality I run into most often.&lt;/p&gt;
&lt;p&gt;A lot of the PHP systems I work with are older revenue-generating applications running on shared hosting, constrained VPS setups, or managed servers where &amp;ldquo;just install another service&amp;rdquo; is not a serious option. They are often business-critical, rarely refactored, and maintained with a pragmatic mindset: keep them running, keep them secure, and avoid unnecessary moving parts.&lt;/p&gt;</description><content type="html"><![CDATA[<p>Most monitoring platforms assume you control the environment.</p>
<p>They assume you can install agents, open ports, run background services, provision a database, and standardize deployment across every machine you touch.</p>
<p>That is not the reality I run into most often.</p>
<p>A lot of the PHP systems I work with are older revenue-generating applications running on shared hosting, constrained VPS setups, or managed servers where &ldquo;just install another service&rdquo; is not a serious option. They are often business-critical, rarely refactored, and maintained with a pragmatic mindset: keep them running, keep them secure, and avoid unnecessary moving parts.</p>
<p>That is the context MATA came out of.</p>
<p>MATA is a lightweight monitoring setup for environments where a full observability stack would be disproportionate to the problem. It is built specifically for PHP-heavy infrastructure and focuses on the kind of visibility that is actually useful when you are responsible for legacy applications with limited deployment options.</p>
<hr>
<h2 id="why-build-this-at-all">Why build this at all?</h2>
<p>When an old PHP application starts misbehaving, the work is usually not glamorous.</p>
<p>It is checking whether the site is still reachable, looking at error logs, checking disk space, reviewing running processes, verifying whether sessions are piling up, and trying to answer a very simple question quickly:</p>
<p><strong>Is the application down, degraded, or just noisy?</strong></p>
<p>There are already excellent monitoring products available, but many of them are designed for environments with more control, more standardization, and more appetite for operational complexity than some small and mid-sized PHP estates actually have.</p>
<p>In the environments I had in mind, the constraints looked more like this:</p>
<ul>
<li>no root access</li>
<li>no long-running agents or exporters</li>
<li>no appetite for adding multiple infrastructure dependencies</li>
<li>inconsistent hosting setups across customers</li>
<li>legacy applications that still need better visibility and alerting</li>
</ul>
<p>That combination matters.</p>
<p>If a monitoring tool is operationally heavier than the application it is meant to protect, it is often not going to be deployed at all. So the goal with MATA was not to compete with enterprise observability platforms. The goal was to build something much narrower and much more practical:</p>
<blockquote>
<p><strong>A monitoring system that can still be deployed in places where the usual answer is &ldquo;we cannot install that here.&rdquo;</strong></p>
</blockquote>
<hr>
<h2 id="what-mata-is">What MATA is</h2>
<p>At a high level, MATA consists of two parts:</p>
<ul>
<li>a central <strong>dashboard</strong></li>
<li>lightweight <strong>read-only nodes</strong> deployed close to the monitored applications</li>
</ul>
<p>The dashboard provides the web interface, alerting, and consolidated visibility across servers and applications.</p>
<p>The nodes expose a small read-only API so the dashboard can pull operational data from each monitored system. That includes basic server metrics, HTTP health information, recent log lines, session counts, and Composer package inventory.</p>
<p>The important design choice is that the system stays intentionally modest.</p>
<p>It is not trying to become a universal telemetry platform. It is trying to answer the questions that tend to matter first in legacy PHP operations:</p>
<ul>
<li>Is the application reachable?</li>
<li>Are there new errors in the logs?</li>
<li>Is the server running out of CPU, RAM, or disk?</li>
<li>Did a dependency version change?</li>
<li>Are sessions behaving unusually?</li>
</ul>
<p>That sounds basic, but in practice it covers a large share of the diagnostics needed to respond quickly when a brittle older system starts acting up.</p>
<hr>
<h2 id="the-design-constraints-that-shaped-it">The design constraints that shaped it</h2>
<p>MATA is very much a product of its environment.</p>
<h3 id="1-it-had-to-work-where-only-php-is-available">1. It had to work where only PHP is available</h3>
<p>A lot of PHP hosting is still conservative. You may get PHP, a web server, and maybe a database — but not much more. That makes heavyweight agents or modern observability sidecars unrealistic.</p>
<p>So the deployment model had to stay simple: if a customer can host a PHP application, they should be able to run a MATA node.</p>
<h3 id="2-it-had-to-be-useful-for-multi-server-oversight">2. It had to be useful for multi-server oversight</h3>
<p>Looking after one legacy application is annoying.
Looking after many small customer systems across different environments is where things get expensive.</p>
<p>The dashboard exists to reduce that friction: one place to review health, logs, alerts, and basic system state without SSH-hopping through multiple servers just to answer routine questions.</p>
<h3 id="3-it-had-to-fail-gracefully">3. It had to fail gracefully</h3>
<p>Monitoring infrastructure is not very helpful if it becomes fragile itself.</p>
<p>For that reason, MATA is designed so the dashboard can continue operating in a degraded mode without a database by falling back to the filesystem for essential behavior. That is a very deliberate trade-off: less architectural elegance, more operational resilience.</p>
<h3 id="4-it-had-to-respect-least-privilege">4. It had to respect least privilege</h3>
<p>The nodes are intentionally read-only.</p>
<p>They do not execute remote actions. They do not send alerts themselves. They do not act as little remote administration agents. They expose a constrained surface so the dashboard can pull the information it needs and nothing more.</p>
<p>That keeps the security model easier to reason about and makes the whole setup more suitable for customer environments where trust boundaries matter.</p>
<hr>
<h2 id="why-this-is-useful-for-legacy-php-estates">Why this is useful for legacy PHP estates</h2>
<p>Legacy systems are rarely failing in especially modern ways.</p>
<p>More often, they fail because a disk fills up, a noisy log starts hiding the real issue, a dependency drifts, a shared host behaves inconsistently, or an application has been patched just enough over the years that small problems cascade into downtime.</p>
<p>For that kind of environment, the most helpful tooling is usually not the most sophisticated tooling. It is the tooling that tells you what changed, what hurts, and where to look first.</p>
<p>That is the niche MATA is aimed at.</p>
<p>Some of the information it can collect includes:</p>
<ul>
<li>CPU, RAM, and disk usage</li>
<li>snapshots of running processes</li>
<li>HTTP status and optional latency checks</li>
<li>recent application log lines</li>
<li>session counts</li>
<li>installed Composer packages and versions</li>
<li>explicitly whitelisted application metadata</li>
</ul>
<p>Just as important is what it does <strong>not</strong> collect:</p>
<ul>
<li>secrets or environment variables</li>
<li>database contents</li>
<li>application source code</li>
</ul>
<p>That boundary matters both operationally and politically. In customer environments, &ldquo;lightweight monitoring&rdquo; tends to be accepted much faster than anything that looks like deep remote introspection.</p>
<hr>
<h2 id="security-model">Security model</h2>
<p>I care a lot about avoiding unnecessary cleverness in security-sensitive tooling.</p>
<p>MATA therefore uses a fairly restrained model:</p>
<ul>
<li><strong>read-only nodes</strong> with no remote action capability</li>
<li><strong>pull-only communication</strong> from dashboard to node</li>
<li><strong>per-node shared secrets</strong> with timestamped HMAC authentication and short TTLs</li>
<li><strong>TLS-only</strong> node access</li>
<li>optional <strong>IP allowlisting</strong></li>
<li><strong>rate limiting</strong> on dashboard and node APIs</li>
<li>CLI-driven user management with audit logging for administrative actions</li>
</ul>
<p>That is not meant to sound flashy. It is meant to be boring in the good sense.</p>
<p>For this kind of tool, boring is desirable. The system should be easy to deploy, easy to understand, and hard to misuse.</p>
<hr>
<h2 id="technology-choices">Technology choices</h2>
<p>MATA is built in PHP on purpose.</p>
<p>If the problem space is PHP operations in constrained environments, there is real value in choosing a stack that is easy to understand and easy to deploy for teams already living in that ecosystem.</p>
<h3 id="php">PHP</h3>
<p>Using PHP for both the dashboard and node side keeps the runtime assumptions small and the deployment story consistent. The requirement is straightforward: if the environment can run modern PHP, it can run MATA.</p>
<h3 id="htmx--bulma">HTMX + Bulma</h3>
<p>On the frontend side, I wanted dynamic behavior without the overhead of a large JavaScript application. HTMX is a good fit for that kind of interface: server-driven, responsive enough for dashboards, and much easier to maintain than a SPA would be in a project like this.</p>
<p>Bulma keeps the UI layer simple and predictable.</p>
<h3 id="twig">Twig</h3>
<p>Twig gives me familiar templating, good ergonomics, and sensible defaults like automatic escaping.</p>
<h3 id="optional-database-usage">Optional database usage</h3>
<p>The dashboard can use MySQL or MariaDB for event logging and configuration, but the system is designed around the idea that some functionality should survive even when a database is unavailable.</p>
<p>Again, this is a pragmatic choice rather than a fashionable one.</p>
<hr>
<h2 id="where-it-fits--and-where-it-does-not">Where it fits — and where it does not</h2>
<p>I think tools benefit from being honest about their scope.</p>
<p>MATA is a good fit for:</p>
<ul>
<li>agencies or small teams managing many customer PHP systems</li>
<li>legacy applications on shared hosting or constrained VPS setups</li>
<li>environments where installing standard agents is impractical</li>
<li>teams that want visibility and alerting without adopting a full observability platform</li>
</ul>
<p>It is not the right tool for:</p>
<ul>
<li>large-scale cloud-native infrastructure</li>
<li>Kubernetes-heavy estates</li>
<li>public dashboards</li>
<li>environments that already support mature observability tooling comfortably</li>
</ul>
<p>If you have full control over a modern platform, there are more powerful and more specialized options available.</p>
<p>But if you are responsible for a collection of older PHP systems that still matter to the business, the equation is different. In that world, a tool that is easy to deploy and reliable under constraints is often more valuable than a more ambitious tool that never makes it into production.</p>
<hr>
<h2 id="what-i-wanted-from-the-project">What I wanted from the project</h2>
<p>From a technical perspective, MATA reflects a preference I have developed more strongly over time:</p>
<p><strong>practical systems beat theoretically perfect ones when the environment is messy.</strong></p>
<p>A lot of engineering work in legacy estates is about accepting constraints without giving up on quality. You may not get to redesign everything. You may not get the ideal infrastructure. You may not get organizational enthusiasm for major refactoring.</p>
<p>But you can still improve visibility.
You can still reduce response time.
You can still make incidents less chaotic.
You can still design tools that are secure, deployable, and respectful of the real-world environments they need to live in.</p>
<p>That is what MATA is really about.</p>
<hr>
<h2 id="closing">Closing</h2>
<p>I did not build MATA because the world needed yet another monitoring product.</p>
<p>I built it because there is a very specific class of PHP applications that still powers real businesses, still generates real revenue, and still tends to get left behind by tooling aimed at cleaner, newer, more standardized environments.</p>
<p>Those systems may not be exciting, but they matter. And when they fail, the people responsible for them need useful answers quickly — not another platform rollout project.</p>
<p>MATA is my attempt to provide that middle ground: lightweight monitoring, sensible security boundaries, and deployment requirements modest enough to work in the places where legacy PHP applications actually live.</p>
<p>If that sounds familiar, you can take a look at the project here:</p>
<ul>
<li>GitHub: <a href="https://github.com/mata-sh/mata-dashboard">https://github.com/mata-sh/mata-dashboard</a></li>
<li>Demo: <a href="https://demo.mata.sh">https://demo.mata.sh</a></li>
</ul>
]]></content></item><item><title>TaskVanguard: LLM-driven task management</title><link>https://blog.xarc.dev/posts/2025/06/taskvanguard-llm-driven-task-management/</link><pubDate>Tue, 10 Jun 2025 10:00:00 +0200</pubDate><guid>https://blog.xarc.dev/posts/2025/06/taskvanguard-llm-driven-task-management/</guid><description>&lt;p&gt;&lt;img src="https://blog.xarc.dev/img/posts/taskvanguard-header.webp" alt="TaskVanguard header image"&gt;&lt;/p&gt;
&lt;p&gt;You type &lt;code&gt;task&lt;/code&gt; into your CLI and get a perfect, color-coded list sorted by urgency.&lt;/p&gt;
&lt;p&gt;One item is marked “high priority.” It has been sitting there for 19 days.&lt;/p&gt;
&lt;p&gt;You know what it is. You know why it matters. And somehow you still close the terminal and end up cleaning the coffee grinder instead.&lt;/p&gt;
&lt;p&gt;I’ve been there: polishing dotfiles instead of fixing a two-line bug because the bug required an uncomfortable conversation.&lt;/p&gt;</description><content type="html"><![CDATA[<p><img src="/img/posts/taskvanguard-header.webp" alt="TaskVanguard header image"></p>
<p>You type <code>task</code> into your CLI and get a perfect, color-coded list sorted by urgency.</p>
<p>One item is marked “high priority.” It has been sitting there for 19 days.</p>
<p>You know what it is. You know why it matters. And somehow you still close the terminal and end up cleaning the coffee grinder instead.</p>
<p>I’ve been there: polishing dotfiles instead of fixing a two-line bug because the bug required an uncomfortable conversation.</p>
<p>TaskVanguard is my attempt to address exactly that moment: an LLM-assisted layer on top of TaskWarrior that helps turn stale tasks into something easier to start.</p>
<hr>
<h2 id="why-task-systems-still-fail-me">Why task systems still fail me</h2>
<p>“Just do it” sounds simple. Usually it isn’t.</p>
<p>The mind negotiates. It looks for alternatives. It suddenly becomes very interested in anything that feels useful enough to count as work.</p>
<p>That is what makes “productivity” tasks so sneaky. Sharpening the tool before starting the work sounds reasonable, right? Organizing tasks, sorting, and prioritizing&hellip; In theory, that makes sense. In practice, it often does not move the needle. Task management itself has become a procrastination activity for me more times than I want to admit.</p>
<blockquote>
<p><strong>Honestly, I think task management is not all that useful beyond a simple list on my desk.</strong></p>
</blockquote>
<p>That realization stings a little after spending hundreds of hours on this topic and endlessly fine-tuning setups. I read David Allen’s <em>Getting Things Done</em>. I tried pretty much every task system I came across. Some of them stuck for months: ThinkingRock, Remember The Milk, Todoist, TickTick, and a few others.</p>
<p>At one point I used Trello and uploaded images for every task so I could visualize them better — which, in retrospect, feels less like productivity and more like arts and crafts.</p>
<p>These days I’ve gone more minimal and landed on TaskWarrior, which I genuinely like. But if I’m being honest, there is a good chance that skipping all of it and using a single <code>todo.txt</code> would have made me more productive.</p>
<p><img src="/img/posts/meme-doing-task.png" alt="Meme about finally doing the task"></p>
<p>Still, if we are going to <del>waste</del> spend time collecting, sorting, and reshuffling tasks, then the best return on that effort is this: reduce the friction to actually start.</p>
<p>This is obviously not a magic fix for procrastination. But I do think there are situations where an LLM can help a little.</p>
<hr>
<h2 id="when-the-system-is-perfect-but-you-still-dont-act">When the system is perfect but you still don’t act</h2>
<p>Let’s say you’ve got TaskWarrior dialed in:</p>
<ul>
<li>Projects and tags for everything</li>
<li>Sophisticated reports and filters</li>
<li>Due dates, priorities, annotations</li>
</ul>
<p>And yet some tasks still just sit there.</p>
<p>The problem isn’t the list. It’s that moment when you see the task, feel the weight of starting, and your brain quietly says: <em>“maybe later.”</em></p>
<p>That gap between knowing what should be done and actually doing it is the real problem. TaskWarrior does not solve it, and neither has any other system I’ve tried so far. These tools can tell you what is urgent. They are much less helpful when procrastination, avoidance, or perfectionism takes over.</p>
<p>Even when I know the pattern, I still fall into it.</p>
<hr>
<h2 id="what-can-an-llm-actually-do-here">What can an LLM actually do here?</h2>
<p>TaskWarrior is a ledger: precise, reliable, unemotional. It will track anything you feed it — but it won’t:</p>
<ul>
<li>Stop you from drifting toward something easier</li>
<li>Tell you what deserves your next 30 minutes</li>
<li>Intervene when perfectionism hijacks your focus</li>
</ul>
<p>TaskVanguard is my attempt to throw an LLM at exactly that problem and lower the friction a bit. It reads your TaskWarrior list and tries to answer a simple question:</p>
<p><strong>What should I do right now?</strong></p>
<p>Think of it as a friend who says:</p>
<blockquote>
<p>“No, don’t redesign the blog header — send the invoice you postponed three times.”</p>
</blockquote>
<hr>
<h2 id="how-taskvanguard-works">How TaskVanguard works</h2>
<p>TaskVanguard connects to TaskWarrior and adds an LLM-powered layer on top. The idea is not to build a smarter database. The idea is to make starting easier.</p>
<p>It can:</p>
<ul>
<li><strong>Reframe vague tasks</strong> into clearer, actionable steps</li>
<li><strong>Auto-tag tasks by impact</strong> (<code>+sb</code> for snowball, <code>+cut</code> for saving time or money, <code>+key</code> for mission-critical)</li>
<li><strong>Split large tasks</strong> into smaller, startable parts</li>
<li><strong>Surface the one thing</strong> to do next, based on your context, mood, and past avoidance</li>
<li><strong>Track goals</strong> so it can highlight tasks that actually move them forward</li>
</ul>
<p>If you keep skipping tasks, it notices. It can ask why, suggest a better framing, and remind you why the task matters in the first place.</p>
<hr>
<h3 id="privacy">Privacy</h3>
<p>Tags and projects can be blacklisted or whitelisted so they are not sent to the LLM. You can use the model of your choice, including a local one. That means you can keep sensitive task data off the cloud.</p>
<hr>
<h2 id="turning-taskwarrior-into-a-thinking-partner">Turning TaskWarrior into a thinking partner</h2>
<p>Here is what that looks like in practice.</p>
<h3 id="reframing-vague-tasks-into-something-you-can-actually-start">Reframing vague tasks into something you can actually start</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ vanguard add <span style="color:#e6db74">&#34;update client documentation&#34;</span> priority:H
</span></span></code></pre></div><p>Result:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Update client documentation with recent changes
</span></span><span style="display:flex;"><span>+fast +key priority:H project:work.dev
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Annotations:
</span></span><span style="display:flex;"><span>- short_reward: reduces support requests
</span></span><span style="display:flex;"><span>- long_reward: improves client self-sufficiency
</span></span><span style="display:flex;"><span>- risk: outdated docs look bad and might cause integration errors
</span></span><span style="display:flex;"><span>- tip: prioritize the sections with most changes first
</span></span></code></pre></div><h3 id="auto-tagging-tasks-by-impact">Auto-tagging tasks by impact</h3>
<p>TaskVanguard can assign tags like:</p>
<ul>
<li><code>+sb</code> for snowball effects</li>
<li><code>+cut</code> for reducing waste</li>
<li><code>+fast</code> for quick wins</li>
<li><code>+key</code> for mission-critical work</li>
</ul>
<p>Example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ vanguard analyze project:work
</span></span></code></pre></div><h3 id="splitting-big-amorphous-tasks-into-actual-steps">Splitting big, amorphous tasks into actual steps</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ vanguard add <span style="color:#e6db74">&#34;write project proposal&#34;</span> project:work.freelancing
</span></span></code></pre></div><p>Result:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ task add draft bullet points <span style="color:#66d9ef">for</span> scope of work +fast +key project:work.freelancing
</span></span><span style="display:flex;"><span>$ task add estimate time needed and costs +fast +key project:work.freelancing
</span></span><span style="display:flex;"><span>$ task add put project proposal together +key project:work.freelancing
</span></span><span style="display:flex;"><span>$ task add send project proposal out +fast +key project:work.freelancing
</span></span></code></pre></div><p>A heavy task turns into a sequence of steps you can actually begin.</p>
<h3 id="surfacing-the-one-thing-to-do-next">Surfacing the one thing to do next</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ vanguard spot
</span></span></code></pre></div><p>Output:</p>
<blockquote>
<p>“Send API access email to client (+fast, +key). Takes ~5 min. Unblocks 3 downstream tasks.”</p>
</blockquote>
<p><img src="/img/posts/vanguard_spot_screenshot_cli.png" alt="TaskVanguard spot command screenshot"></p>
<p>The LLM asks for your mood and context (home/office/travel), then reads your task list, tags, annotations, and urgency scores and picks one task. If you skip it, it tracks that. If you skip it multiple times, it offers to reframe it and invites you to reflect on it.</p>
<hr>
<h3 id="from-knowing-to-doing">From knowing to doing</h3>
<p>A task list is good to have.</p>
<p>But if I use one, what I need most is a tiny bit of momentum.</p>
<p>That is what most systems cannot help with. They might be great at storing and organizing tasks. But as far as I can tell, they do not help me cross the line from <em>I should do this</em> to <em>I’m doing it now</em>.</p>
<p>That is where better framing can help.</p>
<p>You’ve had “Fix onboarding flow” staring at you for weeks. Let’s reframe it, tag it, and point out that it’s blocking a launch milestone. Now it becomes:</p>
<blockquote>
<p>“Refactor onboarding flow to reduce drop-off at step 3 (+sb). Start with reading feedback email — estimated 10 min.”</p>
</blockquote>
<p>That’s a task you can start right now.</p>
<p>Or take the stale <code>priority:H</code> item:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>task add Follow up with Sarah about Q2 projections +finance +email priority:H
</span></span></code></pre></div><p>That becomes:</p>
<blockquote>
<p>“Send Sarah a quick email to confirm the Q2 forecast. It takes &lt;10 min. Skipped twice already. Without it, budget approval stalls — hurting your raise prospects. Start by opening a new email and writing bullet points.”</p>
</blockquote>
<p>That wording might be able to lower the friction of tackling the task just a tiny bit at the exact moment the friction matters most — right before beginning to work on it.</p>
<hr>
<h2 id="closing">Closing</h2>
<p>Whenever I tried getting back into the habit of lifting or running, the best way for me was to put my running shoes right in front of the bed the evening before, get up early, and do it first thing in the morning.</p>
<p>Before my mind wanders.<br>
Before I get distracted by a “better” idea.<br>
Before I give myself one more reason to start tomorrow instead.</p>
<p>Next time you’re staring at that “high priority” task from 19 days ago, it might help if the task comes back to you in a form that feels a little more doable than the one you originally dumped into your todo list.</p>
<p>For some of us, planning and doing need to stay clearly separated. Once it is time to act, thinking less is often better.</p>
<p>Building TaskVanguard was my own way of procrastinating on procrastination. Thanks, past-me. I guess?</p>
<p>If you want to try it, you just need an API key for OpenAI or DeepSeek, or access to a local LLM.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>go install github.com/taskvanguard/taskvanguard/cmd/vanguard@latest
</span></span></code></pre></div>]]></content></item></channel></rss>