<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Harry Agustiana</title>
    <description>The latest articles on DEV Community by Harry Agustiana (@harryagustiana).</description>
    <link>https://dev.to/harryagustiana</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2761971%2Ffecc5812-e25b-43ff-81c4-398560a06443.jpeg</url>
      <title>DEV Community: Harry Agustiana</title>
      <link>https://dev.to/harryagustiana</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/harryagustiana"/>
    <language>en</language>
    <item>
      <title>What a Filesystem Scan Can and Cannot Tell You After a Laravel Intrusion</title>
      <dc:creator>Harry Agustiana</dc:creator>
      <pubDate>Thu, 08 Oct 2026 03:49:28 +0000</pubDate>
      <link>https://dev.to/harryagustiana/what-a-filesystem-scan-can-and-cannot-tell-you-after-a-laravel-intrusion-1hn5</link>
      <guid>https://dev.to/harryagustiana/what-a-filesystem-scan-can-and-cannot-tell-you-after-a-laravel-intrusion-1hn5</guid>
      <description>&lt;p&gt;Most Laravel security tools look at your source code, config, and dependencies before you deploy. That is useful, but it answers one question: &lt;em&gt;is my code safe?&lt;/em&gt; After a compromise, you need a different question answered: &lt;em&gt;has anything on this server been added or changed that should not be there?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That is the gap &lt;a href="https://github.com/hryagstn/laravel-scalpel" rel="noopener noreferrer"&gt;Laravel Scalpel&lt;/a&gt; was built for. It is an open-source intrusion evidence scanner that runs inside a Laravel app and inspects the deployed filesystem.&lt;/p&gt;

&lt;p&gt;A quick update first. Scalpel is now at v1.11.0 with 66 GitHub stars, and it was recently covered by &lt;a href="https://laravel-news.com/laravel-scalpel" rel="noopener noreferrer"&gt;Laravel News&lt;/a&gt;. More important to me, issues and pull requests are now coming from other developers, and the repo has contributors besides me. This post covers how the scanner works and one lesson from the Laravel News test that is worth knowing if you plan to run it in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it checks
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;php artisan scalpel:scan&lt;/code&gt; runs six scanners by default. Five look at the current files, and one compares them with a saved snapshot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structural.&lt;/strong&gt; Executable PHP files in places PHP should never live, by default &lt;code&gt;public/&lt;/code&gt; and &lt;code&gt;storage/&lt;/code&gt;. This covers &lt;code&gt;.php&lt;/code&gt; and less common extensions like &lt;code&gt;.phtml&lt;/code&gt; and &lt;code&gt;.phar&lt;/code&gt;, plus double extensions such as &lt;code&gt;shell.php.jpg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Obfuscated code.&lt;/strong&gt; Common backdoor patterns such as &lt;code&gt;eval(base64_decode(...))&lt;/code&gt;, compressed payload execution, dynamic function calls, direct evaluation of request input, and long encoded strings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.htaccess&lt;/code&gt; and &lt;code&gt;.user.ini&lt;/code&gt;.&lt;/strong&gt; Handler mappings that let the web server run scripts, external redirects, and PHP directives like &lt;code&gt;auto_prepend_file&lt;/code&gt;. That last one is a classic persistence trick, because it runs a hidden file on every request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment.&lt;/strong&gt; A missing or unreadable &lt;code&gt;.env&lt;/code&gt;, a &lt;code&gt;.env&lt;/code&gt; under &lt;code&gt;public/&lt;/code&gt;, an empty &lt;code&gt;APP_KEY&lt;/code&gt;, keys that drift from &lt;code&gt;.env.example&lt;/code&gt;, and &lt;code&gt;APP_DEBUG=true&lt;/code&gt; in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Baseline diff.&lt;/strong&gt; Record a known-good state, then report added, modified, and deleted files later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan scalpel:baseline   &lt;span class="c"&gt;# record SHA-256, size, and mtime&lt;/span&gt;
php artisan scalpel:diff       &lt;span class="c"&gt;# compare the current files against it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the baseline only when you trust the application state, and turn on HMAC signing before you create the first one. A signed baseline lets &lt;code&gt;scalpel:diff&lt;/code&gt; detect a baseline that was regenerated without the signing key.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limit you should understand first
&lt;/h2&gt;

&lt;p&gt;Scalpel runs in the same process and with the same permissions as your application. If an attacker can change your code, they can also change the scanner or its config. A filesystem scan detects evidence of an intrusion. It is not a firewall and it does not contain anything.&lt;/p&gt;

&lt;p&gt;For that reason, run it from an external trigger where possible, keep code directories read-only, and ship results to storage outside the server being scanned.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an independent test found
&lt;/h2&gt;

&lt;p&gt;The Laravel News review ran Scalpel 1.9.0 on a fresh Laravel app. With a clean state and a baseline, the default scan reported nothing. After adding four harmless fixtures (a fake &lt;code&gt;eval(base64_decode(...))&lt;/code&gt; inside &lt;code&gt;if (false)&lt;/code&gt;, an &lt;code&gt;avatar.php.jpg&lt;/code&gt;, an &lt;code&gt;.htaccess&lt;/code&gt; handler mapping, and a &lt;code&gt;.user.ini&lt;/code&gt; with &lt;code&gt;auto_prepend_file&lt;/code&gt;), the content scanners caught them.&lt;/p&gt;

&lt;p&gt;The more interesting result was a false positive pattern. After &lt;code&gt;php artisan optimize&lt;/code&gt; compiled the framework views, the next scan reported about 100 &lt;code&gt;MEDIUM&lt;/code&gt; and two &lt;code&gt;HIGH&lt;/code&gt; findings inside &lt;code&gt;storage/framework/views&lt;/code&gt;. The structural scanner allows that directory, but the obfuscated-code scanner still reads the compiled files.&lt;/p&gt;

&lt;p&gt;If your app caches views in production, test this before you use Scalpel as a deployment gate. You can add &lt;code&gt;storage/framework/views&lt;/code&gt; to &lt;code&gt;content_scan_excluded_paths&lt;/code&gt;, but that means skipping content checks for every compiled view. Alternatively, run &lt;code&gt;php artisan optimize:clear&lt;/code&gt; and recreate the baseline before scanning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it in CI
&lt;/h2&gt;

&lt;p&gt;Both &lt;code&gt;scalpel:scan&lt;/code&gt; and &lt;code&gt;scalpel:diff&lt;/code&gt; support table, JSON, GitHub Actions annotation, and SARIF output. &lt;code&gt;--fail-on&lt;/code&gt; sets the lowest severity that fails a job.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan scalpel:scan &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sarif &lt;span class="nt"&gt;--fail-on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;MEDIUM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code &lt;code&gt;0&lt;/code&gt; means a complete scan with no findings, &lt;code&gt;1&lt;/code&gt; means findings at or above your threshold, and &lt;code&gt;2&lt;/code&gt; means findings below it or an incomplete scan, so an unreadable directory never looks like a clean result. A &lt;code&gt;ScanFinished&lt;/code&gt; event is also dispatched, so you can send your own Slack, mail, or webhook alert without parsing command output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require hryagstn/laravel-scalpel
php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;scalpel-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It needs PHP 8.2+ and supports Laravel 10 to 13. If you hit a false positive or a case it misses, please open an issue. Real-world reports like the one above are what make the scanner better.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source and docs: &lt;a href="https://github.com/hryagstn/laravel-scalpel" rel="noopener noreferrer"&gt;https://github.com/hryagstn/laravel-scalpel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Command simulator: &lt;a href="https://hryagstn.github.io/laravel-scalpel/" rel="noopener noreferrer"&gt;https://hryagstn.github.io/laravel-scalpel/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Almost Shipped a Broken Database on Tencent EdgeOne Makers. Here's What Fixed It</title>
      <dc:creator>Harry Agustiana</dc:creator>
      <pubDate>Sat, 29 Aug 2026 06:25:27 +0000</pubDate>
      <link>https://dev.to/harryagustiana/i-almost-shipped-a-broken-database-on-tencent-edgeone-makers-heres-what-fixed-it-19h2</link>
      <guid>https://dev.to/harryagustiana/i-almost-shipped-a-broken-database-on-tencent-edgeone-makers-heres-what-fixed-it-19h2</guid>
      <description>&lt;p&gt;Here's a mistake I bet a lot of developers coming from traditional hosting will make on their first serverless edge project: assuming a local database file will just... work.&lt;/p&gt;

&lt;p&gt;I made that mistake building &lt;strong&gt;OptiQ&lt;/strong&gt;, a sales-tracking app for small optical shops, on &lt;strong&gt;Tencent EdgeOne Makers&lt;/strong&gt;. This post is less "here's a polished tutorial" and more "here's the debugging path I took," because I think the reasoning matters more than the final answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project, Briefly
&lt;/h2&gt;

&lt;p&gt;Small optical shops have a messy sales pattern that most POS templates don't handle well. A customer might buy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Just a frame&lt;/li&gt;
&lt;li&gt;Just a lens&lt;/li&gt;
&lt;li&gt;A complete pair (frame + lens)&lt;/li&gt;
&lt;li&gt;Softlens&lt;/li&gt;
&lt;li&gt;Small accessories (cleaning solution, frame screws) — no prescription needed
On top of that, the shop needs to track refraction data (OD/OS, cylinder, axis) per customer, and &lt;em&gt;sometimes&lt;/em&gt; link it to a sale — but not always. OptiQ is my answer to that: a small, relational, low-cost app built specifically around that flexibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Low-cost was non-negotiable here. I'm the only IT person at my company, so I don't get to justify infrastructure spend for a side project — it has to run on free tiers, or it doesn't ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake #1: Treating an Edge Function Like a Regular Server
&lt;/h2&gt;

&lt;p&gt;For context, EdgeOne Makers is the rebrand of EdgeOne Pages (as of June 2026) — it grew from "frontend hosting" into a full-stack platform for Web and AI Agent apps, with support for React, Vue, Next.js, Astro, and a few agent frameworks. That expanded scope is what made it worth using for something beyond a static site.&lt;/p&gt;

&lt;p&gt;My plan was simple: static frontend on Pages, API logic on Pages Functions, SQLite file for storage. Deploy was smooth — genuinely, push-to-live in minutes, no CDN or SSL setup needed.&lt;/p&gt;

&lt;p&gt;Then I hit the wall: &lt;strong&gt;edge functions are stateless.&lt;/strong&gt; There's no persistent filesystem, and a given request might get routed to a different edge instance than the last one. A &lt;code&gt;.db&lt;/code&gt; file written on one request could be gone — or inconsistent — by the next. This isn't an EdgeOne-specific limitation; it's true of serverless edge compute generally. But it's an easy thing to &lt;em&gt;forget&lt;/em&gt; if your mental model still comes from VPS-style deployments.&lt;/p&gt;

&lt;p&gt;Lesson: figure out your state strategy before you write a single API route, not after your first weird bug report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake #2 (Almost): Picking a Database That Sleeps When You Need It Awake
&lt;/h2&gt;

&lt;p&gt;Once I accepted I needed a managed database, I evaluated three options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1 — EdgeOne's native KV store.&lt;/strong&gt; Fast, simple, and built in. But it's key-value only — no joins, no relational queries. For OptiQ, that would mean hand-rolling indexes for every access pattern (&lt;code&gt;transactions by customer&lt;/code&gt;, &lt;code&gt;transactions by date range&lt;/code&gt;) and computing monthly totals by pulling records into app code and summing manually. It works, but it's the kind of thing that quietly turns into technical debt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2 — Supabase.&lt;/strong&gt; Full Postgres, generous free tier, great DX. Then I read the fine print: free projects &lt;strong&gt;pause after ~7 days of inactivity&lt;/strong&gt; and need a manual restore to wake back up. A shop that doesn't sell something every single day could open the app to a database that's asleep. That's a bad failure mode for a business tool, even a small one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 3 — Turso.&lt;/strong&gt; SQLite (libSQL), but run as a managed service and queried over HTTP — which means it works fine from a stateless edge function, unlike a local file. Real SQL (joins, constraints, aggregates), no idle-pause behavior. This is what I went with, and the schema I'd already sketched needed almost zero changes to work with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Part of the Schema That Actually Matters
&lt;/h2&gt;

&lt;p&gt;Most of OptiQ's schema is unremarkable — customers, products, transactions, transaction line items. The one decision worth calling out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;AUTOINCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;refraction_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;refractions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;-- nullable, on purpose&lt;/span&gt;
  &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;refraction_id&lt;/code&gt; is nullable. That's the whole trick. Without it, I'd have needed separate transaction tables (or awkward sentinel values) for "sale with a prescription attached" vs "sale without one" — like buying a bottle of lens cleaner shouldn't require a fake prescription record just to satisfy a NOT NULL constraint. One nullable foreign key instead of a schema fork.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where EdgeOne Makers' Full-Stack Positioning Actually Paid Off
&lt;/h2&gt;

&lt;p&gt;The thing I didn't expect going in: not having to stitch together three separate vendors. Static hosting, edge API functions, and (once I wired it up) the database connection all lived in one deploy pipeline. If you've ever debugged a CORS or env-var mismatch between a frontend host, a serverless function provider, and a database vendor that don't share tooling, you'll get why that's worth mentioning.&lt;/p&gt;

&lt;h2&gt;
  
  
  If You're About to Build Something Similar
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Decide your state/database approach &lt;em&gt;before&lt;/em&gt; your first API route — not as a fix after deploying.&lt;/li&gt;
&lt;li&gt;Check what happens to your database on &lt;em&gt;inactivity&lt;/em&gt;, not just under load. "Free tier" often hides a failure mode in the fine print.&lt;/li&gt;
&lt;li&gt;Use nullable foreign keys deliberately when your domain has "sometimes this relationship exists, sometimes it doesn't" — it's cheaper than modeling every variant as a separate table.
OptiQ is live at &lt;a href="https://optiq.logota.biz.id/" rel="noopener noreferrer"&gt;optiq.logota.biz.id&lt;/a&gt; — built on Tencent EdgeOne Makers as part of Codepolitan's DevHandal 2026 Batch 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;#TencentEdgeOne #EdgeOneMakers #CODEPOLITAN #EdgeOne&lt;/p&gt;

</description>
      <category>tencentedgeone</category>
      <category>edgeonemakers</category>
      <category>codepolitan</category>
      <category>edgeone</category>
    </item>
    <item>
      <title>How a Broken Laravel App Turned Into Two Open Source Security Tools</title>
      <dc:creator>Harry Agustiana</dc:creator>
      <pubDate>Mon, 13 Jul 2026 05:11:32 +0000</pubDate>
      <link>https://dev.to/harryagustiana/how-a-broken-laravel-app-turned-into-two-open-source-security-tools-287c</link>
      <guid>https://dev.to/harryagustiana/how-a-broken-laravel-app-turned-into-two-open-source-security-tools-287c</guid>
      <description>&lt;p&gt;I now run a small self-hosted watchtower that pings me on Telegram the moment something looks wrong on any Laravel app I manage. It didn't start as a plan. It started with a friend's text message and a server that had already been compromised for who knows how long.&lt;/p&gt;

&lt;h2&gt;
  
  
  The app nobody thought was a target
&lt;/h2&gt;

&lt;p&gt;The app was PaketLebaranku.id — a small Laravel + MySQL + Livewire platform running on a modest VPS through Biznet Gio, built for a friend's small business. Its only job: track installment savings for people paying into a Lebaran package program, small recurring deposits that add up to something meaningful once a year.&lt;/p&gt;

&lt;p&gt;It's exactly the kind of app that never shows up in anyone's threat model. No enterprise budget, no dedicated ops team, no security monitoring — just a business owner and a friend who built the thing on the side. Which, as it turned out, is precisely why it got hit.&lt;/p&gt;

&lt;h2&gt;
  
  
  "The app is throwing a 500 error, can you check?"
&lt;/h2&gt;

&lt;p&gt;That was the whole message. I expected a stack trace, maybe a bad migration or an expired dependency. Instead, digging into the error turned up something far worse.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.env&lt;/code&gt; file wasn't misconfigured — it was gone. Any Laravel developer knows what that means: database credentials, app keys, third-party secrets, either already exposed or already pulled by whoever got in.&lt;/p&gt;

&lt;p&gt;Then a second find: a handful of backdoor scripts sitting inside &lt;code&gt;public/icons&lt;/code&gt;, dressed up to look like they belonged there. Then a third: &lt;code&gt;.htaccess&lt;/code&gt; had been rewritten to let Python scripts execute inside a directory that should never be able to run anything.&lt;/p&gt;

&lt;p&gt;Three separate footholds, sitting quietly in an app tied to real people's savings, for who knows how long before a user happened to hit an error page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap that actually mattered
&lt;/h2&gt;

&lt;p&gt;Once we'd contained it, the obvious question was "how do I catch this faster next time." But sitting with it longer, I realized the real failure wasn't a missing check — it was that nothing was watching at all. A scan run once, manually, after the fact doesn't help. The incident wasn't caught by a security tool; it was caught by luck, in the form of an annoyed user.&lt;/p&gt;

&lt;p&gt;That reframed what I actually needed to build: not just something that &lt;em&gt;could&lt;/em&gt; find this class of problem, but something that would look for it &lt;em&gt;without anyone having to remember to ask&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Laravel Scalpel: the checks I wish had already existed
&lt;/h2&gt;

&lt;p&gt;The first piece is &lt;strong&gt;Laravel Scalpel&lt;/strong&gt;, a narrow, fast forensic scanner for Laravel codebases. It doesn't try to be a full SAST suite — it answers the exact three questions that would have shortened this incident from days to minutes: are there files sitting where they shouldn't be, does the environment configuration look right, are there permission or execution paths that shouldn't exist.&lt;/p&gt;

&lt;p&gt;It's picked up some traction beyond my own use case — featured by the Laravel Artisan Community, 24+ GitHub stars, and a pull request currently open to get it listed in &lt;code&gt;awesome-laravel&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  N8N Bastion: making sure someone's actually watching
&lt;/h2&gt;

&lt;p&gt;The second piece, &lt;strong&gt;N8N Bastion&lt;/strong&gt;, is what closes the real gap. It's a self-monitoring stack built on n8n that turns infrastructure and application problems into a Telegram message — instead of a support ticket that arrives after the damage is done. Laravel Scalpel now runs inside it as a scheduled check, not a tool anyone has to remember to invoke.&lt;/p&gt;

&lt;p&gt;Together they cover both halves of the problem: Scalpel knows what to look for on a Laravel app, and Bastion makes sure it's actually looking, on a schedule, without a human in the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is worth open-sourcing
&lt;/h2&gt;

&lt;p&gt;PaketLebaranku.id will never make a breach headline. It's too small, too obscure, too ordinary — and that's exactly the point. There are a lot of small businesses running real, sensitive workloads on a single self-managed VPS with no monitoring at all, not because the owners don't care, but because every security tool out there quietly assumes a team and a budget they don't have.&lt;/p&gt;

&lt;p&gt;Laravel Scalpel and N8N Bastion exist because of one real incident on one real app. I'm putting both out there because I'd bet a lot of other small Laravel apps are sitting exactly where PaketLebaranku.id was, right before that 500 error showed up.&lt;/p&gt;

&lt;p&gt;If you run a Laravel app on a VPS you manage yourself, I'd genuinely like to know what breaks or what's missing if you try these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel Scalpel: &lt;a href="https://github.com/hryagstn/laravel-scalpel" rel="noopener noreferrer"&gt;https://github.com/hryagstn/laravel-scalpel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;N8N Bastion: &lt;a href="https://github.com/hryagstn/n8n-bastion" rel="noopener noreferrer"&gt;https://github.com/hryagstn/n8n-bastion&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
  </channel>
</rss>
