<?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: Bartosz Osiej</title>
    <description>The latest articles on DEV Community by Bartosz Osiej (@bartoszosiej).</description>
    <link>https://dev.to/bartoszosiej</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%2F4088500%2F7c989efc-dcb0-43df-9909-e04efd402bdf.png</url>
      <title>DEV Community: Bartosz Osiej</title>
      <link>https://dev.to/bartoszosiej</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bartoszosiej"/>
    <language>en</language>
    <item>
      <title>Building a Solana-like validator in Rust: what PoH, Tower BFT and Sealevel actually force you to think about</title>
      <dc:creator>Bartosz Osiej</dc:creator>
      <pubDate>Tue, 15 Sep 2026 15:52:48 +0000</pubDate>
      <link>https://dev.to/bartoszosiej/building-a-solana-like-validator-in-rust-what-poh-tower-bft-and-sealevel-actually-force-you-to-14h9</link>
      <guid>https://dev.to/bartoszosiej/building-a-solana-like-validator-in-rust-what-poh-tower-bft-and-sealevel-actually-force-you-to-14h9</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;TrustNode is a from-scratch Solana-style cluster in Rust: a verified Proof-of-History clock, Tower BFT-style consensus, a Sealevel-like parallel execution engine, gossip, and erasure-coded block recovery. The point was never to clone Solana — it's that these three subsystems force concrete, painful design decisions that generic "blockchain in Rust" tutorials skip. This is what they actually force.&lt;/p&gt;

&lt;h2&gt;
  
  
  The PoH clock is a scheduling problem, not a hash chain
&lt;/h2&gt;

&lt;p&gt;The naive description — "hash a counter, publish a chain of hashes" — is one line. The real problem is that the clock sources events (&lt;code&gt;TickHeight&lt;/code&gt;, sequential slot heights) and the &lt;em&gt;validators&lt;/em&gt; re-derive them locally to trust the chain. When it breaks, it breaks as ordering: two validators disagree on which tick a transaction belonged to, and the whole ledger forks at that point.&lt;/p&gt;

&lt;p&gt;What actually mattered in the implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Batching ticks, not hashing every transaction.&lt;/strong&gt; Hash a counter on each tick, but let a batch of entries commute into one tick. Constant re-hashing per-transaction kills throughput and makes the clock the bottleneck.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The clock must be derivable from the ledger.&lt;/strong&gt; If "slot N belongs to slot N" isn't an independent statement any node can recompute from the block alone, you've built a chain that needs a trusted signer — the exact thing you were trying to cryptographically remove.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verifier side must match generator side bit-for-bit.&lt;/strong&gt; Off-by-one in the tick batching turns up as a consensus failure weeks later, not a compile error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lesson: in a PoH system the clock &lt;em&gt;is&lt;/em&gt; the consensus substrate. Get the pure function right first; consensus is downstream of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tower BFT is a voting game at fixed heights
&lt;/h2&gt;

&lt;p&gt;Tower BFT simplifies PBFT by anchoring votes to the PoH slot height. The trick (and the trap) is that votes happen at &lt;em&gt;heights&lt;/em&gt;, and each validator commits to "I have not voted against this fork above slot X for Y slots." The consequence that's easy to miss until you implement it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vote lifetime is a number you have to choose.&lt;/strong&gt; How many slots does a lock hold? Too short and liveness collapses (validators flip-flop, the fork battle never ends); too long and the network can be bricked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit vs. finalize are different states.&lt;/strong&gt; Reaching "commit" as a local statement is easy; broadcasting finalization so &lt;em&gt;other&lt;/em&gt; nodes can rely on it is where cloudblocks appear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The lock mechanism is per-validator bookkeeping.&lt;/strong&gt; Track highest lock height, refuse to vote below it, and explain that in tests — because half the bugs turn out to be "validator voted against its own lock."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lesson: BFT flavors differ in &lt;em&gt;where they put the voting rules&lt;/em&gt;, not in whether they have them. Implementing one in your own repo makes the whitepaper read like a checklist instead of a mystery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sealevel parallelism lives or dies on account locks
&lt;/h2&gt;

&lt;p&gt;Parallel execution is the marketing line; the implementation is "which accounts does this instruction touch, and can I prove they don't overlap." The real work is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static account-readset/writeset extraction.&lt;/strong&gt; Every instruction declares accounts before execution. If it doesn't, you can't schedule safely — so the API forces it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overlap detection decides throughput.&lt;/strong&gt; Two programs touching disjoint accounts run in parallel; any overlap serializes. Most of the performance cliff lives in a naive overlap check (collision on account keys, not program IDs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The price of conflict is determinism.&lt;/strong&gt; Any scheduler that reorders conflicting instructions must be &lt;em&gt;reproducible across all validators&lt;/em&gt;, or the same block executes differently on different machines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lesson: "parallel VM" is 20% scheduling and 80% proving it's deterministic while parallel.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd build differently
&lt;/h2&gt;

&lt;p&gt;Real transaction processing over RPC is the current frontier in the repo (commits land almost daily). The honest retro:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Boot the consensus before the clock.&lt;/strong&gt; I built PoH first because it looks like the foundation. In hindsight the vote/lock rules are the design core; the clock is just the substrate they sit on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fuzz the ledger reconstruction.&lt;/strong&gt; Erasure-coded recovery looks simple until a node survives with 2-of-4 shards and has to rebuild &lt;em&gt;without&lt;/em&gt; trusting what it already has.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Determinism tests on the scheduler early.&lt;/strong&gt; Portable scheduling is the difference between a demo and a network.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Repo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/BartoszOsiej/TrustNode" rel="noopener noreferrer"&gt;TrustNode&lt;/a&gt; — PoH clock, Tower BFT, Sealevel-style execution, gossip + erasure coding, real transaction processing over RPC (Rust, MIT).&lt;/p&gt;

</description>
      <category>rust</category>
      <category>blockchain</category>
      <category>consensus</category>
      <category>systems</category>
    </item>
    <item>
      <title>eBPF verifier limits are a design constraint: what CO-RE field offsets and bounded loops taught me</title>
      <dc:creator>Bartosz Osiej</dc:creator>
      <pubDate>Tue, 15 Sep 2026 15:52:16 +0000</pubDate>
      <link>https://dev.to/bartoszosiej/ebpf-verifier-limits-are-a-design-constraint-what-co-re-field-offsets-and-bounded-loops-taught-me-1akc</link>
      <guid>https://dev.to/bartoszosiej/ebpf-verifier-limits-are-a-design-constraint-what-co-re-field-offsets-and-bounded-loops-taught-me-1akc</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Working on talus-process-monitor (Rust userspace + libbpf/C eBPF) I kept bumping into the same three walls: CO-RE field offsets shifting between kernel versions, the verifier refusing anything that looks like an unbounded loop, and map access patterns that get rejected at load time. All three are &lt;em&gt;design&lt;/em&gt; constraints, not compiler annoyances. This post is what changed in my approach once I stopped fighting them.&lt;/p&gt;

&lt;h2&gt;
  
  
  CO-RE relocation is a promise you have to verify
&lt;/h2&gt;

&lt;p&gt;Portable BPF (Compile Once - Run Everywhere) means the kernel rewrites your field accesses based on its own BTF. In theory: compile once, run on kernel 6.x. In practice I chased two classes of bugs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;struct file&lt;/code&gt; layout differences&lt;/strong&gt; — an offset that's valid on kernel A and wrong on kernel B. The relocation did its job; &lt;em&gt;I&lt;/em&gt; had assumed a field meant what it meant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unavailable BTF&lt;/strong&gt; — if the target kernel doesn't ship BTF, relocation fails at load with a cryptic error. It's a deployment check, not a code bug.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What made this tractable: a &lt;strong&gt;verification matrix in CI&lt;/strong&gt; — the same source compiled and &lt;em&gt;loaded&lt;/em&gt; against a set of kernels, with the load result asserted. When a field offset drifts, the pipeline turns red before a cluster does.&lt;/p&gt;

&lt;p&gt;Body of the lesson: "portable" is a claim that must be tested per-kernel, or it's just hope. A build that passes on one kernel is a demo, not portability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The verifier forbids unbounded loops. That's fine; it teaches you state machines.
&lt;/h2&gt;

&lt;p&gt;The verifier allows bounded loops (with a maximum iteration count) and rejects anything unverifiable. If you want to scan "all entries in this hash map" you get rejected at load time. The productive response is to stop thinking "iterate until done" and think "fixed horizon, amortized":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lease the map.&lt;/strong&gt; Instead of iterating the whole map per event, keep a fixed-size sliding window of state and, on overflow, process the oldest batch — a bounded, verifier-friendly step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Folding state into the map entry.&lt;/strong&gt; Store the running score &lt;em&gt;in&lt;/em&gt; the entry being updated, so decisions don't require iterating unrelated entries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Push the unbounded part userspace.&lt;/strong&gt; The kernel probe exports a summary; the Rust side does the arbitrary-loop reasoning.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This mirrors how real agents behave: kernel-side reaction is quick and bounded; the risky/flexible analysis happens outside the hot path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Map access patterns decide load-time rejection
&lt;/h2&gt;

&lt;p&gt;Two patterns get rejected at unexpected places: holding an entry pointer across a &lt;code&gt;perf&lt;/code&gt; output call, and nested lookup while a lookup key is pinned. The verifier tracks &lt;em&gt;what you are allowed to do while a value pointer is live&lt;/em&gt;. Restructure: copy the fields you need into stack-local values, then emit events. It's the difference between "loads everywhere" and "loads only when the code is shaped right."&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification has to live in CI, not in your head
&lt;/h2&gt;

&lt;p&gt;The current talus trunk carries a &lt;strong&gt;verification document&lt;/strong&gt; (field-offset matrix, per-kernel load expectations) plus CI that compiles and asserts. The single highest-value commit this project got was not a feature — it was the CI job that turned "should work on this kernel" into a machine-checked statement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/BartoszOsiej/talus-process-monitor" rel="noopener noreferrer"&gt;talus-process-monitor&lt;/a&gt; — Rust + libbpf/C eBPF endpoint security agent with a MeMLP neural detection engine; verification matrix lives in &lt;a href="https://github.com/BartoszOsiej/talus-process-monitor/blob/master/VERIFICATION-EBPF.md" rel="noopener noreferrer"&gt;VERIFICATION-EBPF.md&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ebpf</category>
      <category>rust</category>
      <category>kernel</category>
      <category>linux</category>
    </item>
    <item>
      <title>Catching ransomware with eBPF: what execve/openat tracing taught me about false positives</title>
      <dc:creator>Bartosz Osiej</dc:creator>
      <pubDate>Tue, 15 Sep 2026 15:52:15 +0000</pubDate>
      <link>https://dev.to/bartoszosiej/catching-ransomware-with-ebpf-what-execveopenat-tracing-taught-me-about-false-positives-4676</link>
      <guid>https://dev.to/bartoszosiej/catching-ransomware-with-ebpf-what-execveopenat-tracing-taught-me-about-false-positives-4676</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;talus-process-monitor is an eBPF-based ransomware detector for Linux: it traces &lt;code&gt;execve&lt;/code&gt;/&lt;code&gt;openat&lt;/code&gt; from the kernel, streams events over per-CPU perf buffers, and flags behavioural patterns — mass file rewrites plus extension churn — in real time. The hard part is not the tracing; it's scoring patterns without crying wolf on every &lt;code&gt;cargo build&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why behaviour, not signatures
&lt;/h2&gt;

&lt;p&gt;Signature detection on Linux ransomware is nearly useless — most samples are short-lived, often scripts wrapping legitimate tools (&lt;code&gt;find&lt;/code&gt;, &lt;code&gt;mv&lt;/code&gt;, &lt;code&gt;gzip&lt;/code&gt; in a loop). What you &lt;em&gt;can&lt;/em&gt; catch is the shape of the damage: hundreds of files opened, rewritten, renamed or re-extensioned within seconds across directories the process has no business touching.&lt;/p&gt;

&lt;p&gt;So talus watches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;execve&lt;/code&gt;/&lt;code&gt;execveat&lt;/code&gt;&lt;/strong&gt; — what process tree is doing this?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;openat&lt;/code&gt;/&lt;code&gt;openat2&lt;/code&gt;&lt;/strong&gt; — with which flags? (&lt;code&gt;O_WRONLY|O_CREAT&lt;/code&gt; churn is the signature move)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;unlink&lt;/code&gt;/&lt;code&gt;rename&lt;/code&gt;&lt;/strong&gt; — destruction and renaming patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of it from kernel probes, before the data hits disk crypto.&lt;/p&gt;

&lt;h2&gt;
  
  
  The plumbing: per-CPU perf buffers or nothing
&lt;/h2&gt;

&lt;p&gt;First mistake I made: a single global ring buffer. Under parallel load (which is exactly when ransomware runs — it wants throughput), events collide and drop. The fix is standard but worth repeating: &lt;strong&gt;per-CPU perf buffers&lt;/strong&gt; with a userspace loader pinning CPUs and reassembling event order per process.&lt;/p&gt;

&lt;p&gt;The userspace side is Rust; the eBPF side is libbpf/C with CO-RE. Compile once, run across kernel 6.x — in theory. In practice, verify your field offsets: &lt;code&gt;struct file&lt;/code&gt; layout differences bit me twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scoring problem: build systems are innocent ransomware
&lt;/h2&gt;

&lt;p&gt;A naive "N files written in T seconds" rule fires constantly on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cargo build&lt;/code&gt; touching thousands of files in &lt;code&gt;target/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;kernel module compilation&lt;/li&gt;
&lt;li&gt;any test suite with fixtures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three heuristics that made the detector usable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extension churn, not just writes.&lt;/strong&gt; Ransomware renames (&lt;code&gt;doc.docx&lt;/code&gt; → &lt;code&gt;doc.docx.locked&lt;/code&gt;) or re-extensions in bulk. Builders write new files but rarely &lt;em&gt;rename&lt;/em&gt; existing ones at volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write-after-read density.&lt;/strong&gt; Encryptors must read the plaintext before rewriting it. A builder writes fresh output without having read those exact files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directory breadth vs. depth.&lt;/strong&gt; &lt;code&gt;target/&lt;/code&gt; is one deep subtree; ransomware sweeps breadth-first across &lt;code&gt;$HOME&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these is a silver bullet; the score is a weighted blend, and the weights are the actual product.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ship the &lt;strong&gt;audit mode first&lt;/strong&gt;: weeks of logs on a real desktop before enforcing anything. False-positive rates you &lt;em&gt;guess&lt;/em&gt; are wrong.&lt;/li&gt;
&lt;li&gt;Make the threshold config per-directory-class (&lt;code&gt;build-dirs&lt;/code&gt;, &lt;code&gt;home&lt;/code&gt;, &lt;code&gt;media&lt;/code&gt;), not global.&lt;/li&gt;
&lt;li&gt;eBPF verifier limits are a design constraint from day one — bounded loops, no unbounded map iteration — or you'll redesign the detection logic twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Repo
&lt;/h2&gt;

&lt;p&gt;Code, architecture notes and verification checklist: &lt;a href="https://github.com/BartoszOsiej/talus-process-monitor" rel="noopener noreferrer"&gt;talus-process-monitor&lt;/a&gt; (Rust + libbpf/C, Linux 6.x).&lt;/p&gt;

</description>
      <category>ebpf</category>
      <category>rust</category>
      <category>linux</category>
      <category>security</category>
    </item>
    <item>
      <title>A web OS that 'never booted': the two bugs that looked like kernel failures but were 404s and z-index</title>
      <dc:creator>Bartosz Osiej</dc:creator>
      <pubDate>Tue, 15 Sep 2026 15:51:44 +0000</pubDate>
      <link>https://dev.to/bartoszosiej/a-web-os-that-never-booted-the-two-bugs-that-looked-like-kernel-failures-but-were-404s-and-1p3</link>
      <guid>https://dev.to/bartoszosiej/a-web-os-that-never-booted-the-two-bugs-that-looked-like-kernel-failures-but-were-404s-and-1p3</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Aurora is a browser-based OS — window manager, virtual file system, terminal, 8 apps — straight TypeScript with zero runtime deps. For two days it booted to a spinner and hung on "Initializing kernel…". The kernel never had a bug. The app was loading &lt;code&gt;dist/main.js&lt;/code&gt; which did not exist on GitHub Pages, because &lt;code&gt;dist/&lt;/code&gt; is gitignored and Pages serves from the repo. Then, after fixing that, the desktop icons silently ignored clicks. Second bug: a full-bleed layer at &lt;code&gt;z-index: 2&lt;/code&gt; swallowed every pointer event meant for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug one: GitHub Pages does not run your build
&lt;/h2&gt;

&lt;p&gt;The page referenced &lt;code&gt;dist/style.css&lt;/code&gt; and &lt;code&gt;dist/main.js&lt;/code&gt;. The repo had &lt;code&gt;.gitignore&lt;/code&gt; ignoring &lt;code&gt;dist/&lt;/code&gt;. GitHub Pages does not execute &lt;code&gt;npm run build&lt;/code&gt; for a static site source — it serves the committed tree. Result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML: 200 (committed)&lt;/li&gt;
&lt;li&gt;assets: 404 (never committed)&lt;/li&gt;
&lt;li&gt;boot: stuck at "Initializing kernel…" forever&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI text made it look like a kernel hang, because the boot sequence awaited a bundle that would never arrive. &lt;strong&gt;The fix was a one-liner that should have been a CI job:&lt;/strong&gt; commit the built output, or publish Pages from the &lt;code&gt;dist/&lt;/code&gt; folder. I built the bundle and force-added it to the repo.&lt;/p&gt;

&lt;p&gt;The deeper lesson is about failure mode. "OS stuck on kernel init" reads as a deep systems bug. It was a static-file deployment miss. The boot screen gives you no diagnostic; the console gives you a 404. Always check the network tab before the CPU.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug two: the invisible layer that eats clicks
&lt;/h2&gt;

&lt;p&gt;After the bundles went live, the OS booted — but desktop icons (double-click to launch) did nothing. Single click &lt;em&gt;did&lt;/em&gt; select, so it felt random. The real cause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#windows-layer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A transparent full-viewport layer sits &lt;strong&gt;above&lt;/strong&gt; the icon grid (&lt;code&gt;z-index: 1&lt;/code&gt;). Even empty, an element with positive z-index and default &lt;code&gt;pointer-events: auto&lt;/code&gt; captures pointer events that cross it. Icons never received the events; selection worked because… actually it didn't belong to that layer, but the launch (double-click) path went through the window layer's parent.&lt;/p&gt;

&lt;p&gt;The fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#windows-layer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#windows-layer&lt;/span&gt; &lt;span class="nc"&gt;.win&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click-through by default; opt-in only for actual windows. This pattern belongs in every layered UI: an empty layout container should never block input.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Walk the asset tree after every Pages deploy.&lt;/strong&gt; &lt;code&gt;curl&lt;/code&gt; every referenced &lt;code&gt;dist/*&lt;/code&gt; file. A missing bundle is a 30-second check, not a two-day mystery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit the build or CI-render it.&lt;/strong&gt; Pick one: either Pages builds from &lt;code&gt;dist/&lt;/code&gt; (plugin) or the repo carries the bundle. Decide in the repo README so it doesn't drift again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default pointer-events to none on layout scaffolding.&lt;/strong&gt; Only interactive surfaces (windows, buttons) should claim pointer events; containers should be transparent to input by policy, not by luck.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Repo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/BartoszOsiej/Aurora" rel="noopener noreferrer"&gt;Aurora&lt;/a&gt; — a complete browser OS: window manager, VFS, terminal (35+ commands), 8 apps, procedural audio (TypeScript, zero runtime deps).&lt;/p&gt;

</description>
      <category>web</category>
      <category>typescript</category>
      <category>debugging</category>
      <category>deploy</category>
    </item>
    <item>
      <title>I made my portfolio readable to AI crawlers: llms.txt, JSON-LD and a 40-method distribution stack</title>
      <dc:creator>Bartosz Osiej</dc:creator>
      <pubDate>Tue, 15 Sep 2026 15:51:43 +0000</pubDate>
      <link>https://dev.to/bartoszosiej/i-made-my-portfolio-readable-to-ai-crawlers-llmstxt-json-ld-and-a-40-method-distribution-stack-144p</link>
      <guid>https://dev.to/bartoszosiej/i-made-my-portfolio-readable-to-ai-crawlers-llmstxt-json-ld-and-a-40-method-distribution-stack-144p</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Search is splitting in two: the classic index and the answer engines. This playbook covers both with zero budget: &lt;code&gt;llms.txt&lt;/code&gt; + &lt;code&gt;llms-full.txt&lt;/code&gt; for AI citation, JSON-LD entity graphs for the Knowledge Graph, robots.txt rules for AI bots, IndexNow for instant indexing, and a git-push-triggered cross-posting pipeline that ships every article to dev.to, Hashnode, Bluesky, Mastodon and LinkedIn automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: answer engines don't read your website the way Google does
&lt;/h2&gt;

&lt;p&gt;When ChatGPT, Claude or Perplexity answer "who builds eBPF security tooling in Poland?", they are not running PageRank. They cite sources that are &lt;em&gt;easy to quote&lt;/em&gt;: clean entity definitions, explicit relationships, structured facts. A beautiful portfolio page with everything implied in JavaScript is invisible to them.&lt;/p&gt;

&lt;p&gt;I run my whole web presence on GitHub Pages — no backend, no budget. Here is the stack that makes it machine-readable anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. llms.txt and llms-full.txt — the entity card
&lt;/h2&gt;

&lt;p&gt;At the root of my domain sit two files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;llms.txt&lt;/code&gt;&lt;/strong&gt; — a concise map: who I am, canonical links, project list with one-line descriptions. Follows the &lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;llmstxt spec&lt;/a&gt;: H1 name, blockquote summary, H2 sections of Markdown links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;llms-full.txt&lt;/code&gt;&lt;/strong&gt; — the long form: per-project architecture notes and, crucially, &lt;strong&gt;subject–predicate–object triples&lt;/strong&gt; (24 for the Person entity, 42+ technical, 19 for the book series). Triples are the format knowledge graphs are built from — you are effectively pre-chewing entity extraction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key mindset shift: stop writing only for readers, start also writing &lt;em&gt;assertions&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. JSON-LD — one &lt;a class="mentioned-user" href="https://dev.to/graph"&gt;@graph&lt;/a&gt;, one source of truth
&lt;/h2&gt;

&lt;p&gt;Every page carries a &lt;code&gt;Person&lt;/code&gt; node with a stable &lt;code&gt;@id&lt;/code&gt; (&lt;code&gt;https://bartoszosiej.github.io/#person&lt;/code&gt;). Project pages carry &lt;code&gt;SoftwareApplication&lt;/code&gt; nodes that reference the person via &lt;code&gt;{ "@id": "..." }&lt;/code&gt; instead of repeating the data. The books site carries &lt;code&gt;BookSeries&lt;/code&gt; → 3 &lt;code&gt;Book&lt;/code&gt; nodes with ASINs.&lt;/p&gt;

&lt;p&gt;Cross-references matter more than the individual nodes: &lt;code&gt;author&lt;/code&gt;, &lt;code&gt;creator&lt;/code&gt;, &lt;code&gt;sameAs&lt;/code&gt; (GitHub, dev.to) are what let a crawler stitch "Bartosz Osiej" on GitHub and "Bartosz Osiej" on the books site into one entity.&lt;/p&gt;

&lt;p&gt;On Docusaurus, &lt;code&gt;headTags&lt;/code&gt; in &lt;code&gt;docusaurus.config.ts&lt;/code&gt; injects these on every build, reading the JSON from &lt;code&gt;static/schema/&lt;/code&gt;. No plugin, no JavaScript at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. robots.txt — explicitly invite the AI bots
&lt;/h2&gt;

&lt;p&gt;The default &lt;code&gt;User-agent: *&lt;/code&gt; block does not always cover AI crawlers. Name them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight robot_framework"&gt;&lt;code&gt;User-agent: GPTBot&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Allow:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;User-agent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ClaudeBot&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Allow:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;User-agent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;PerplexityBot&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Allow:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;User-agent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Google-Extended&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Allow:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to be cited, allow the citation engines. (Reverse the policy if you're protecting paid content — but then don't wonder why you're not in the answers.)&lt;/p&gt;

&lt;h2&gt;
  
  
  4. IndexNow — skip the crawl queue
&lt;/h2&gt;

&lt;p&gt;IndexNow (Bing, Yandex, Seznam; Naver) accepts a JSON POST of changed URLs and gets them indexed in hours instead of weeks. The "key" is not a secret — it's a proof-of-control token you serve publicly at &lt;code&gt;/&amp;lt;KEY&amp;gt;.txt&lt;/code&gt;. A 30-line GitHub Action pings my whole sitemap on every push and weekly on a cron. Google still needs Search Console + a submitted sitemap, but Bing-family indexing is now instant.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The distribution pipeline — one push, nine platforms
&lt;/h2&gt;

&lt;p&gt;Writing the article is 20% of the work; shipping it used to be the other 80%. Now it's a &lt;code&gt;git push&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Article lands in &lt;code&gt;content/&lt;/code&gt; with YAML front matter (&lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt;, &lt;code&gt;canonical_url&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A GitHub Action runs &lt;a href="https://github.com/queelius/crier" rel="noopener noreferrer"&gt;&lt;code&gt;crier&lt;/code&gt;&lt;/a&gt; in &lt;code&gt;--batch&lt;/code&gt; mode: dev.to and Hashnode get the full article &lt;strong&gt;with &lt;code&gt;canonical_url&lt;/code&gt; pointing back at my domain&lt;/strong&gt; — so Google counts the link equity on my site, not the syndicate copy. Bluesky and Mastodon get auto-generated short posts with the link; Telegram and Discord channels get announcements.&lt;/li&gt;
&lt;li&gt;A SQLite registry (cached + committed back by the workflow) makes every publish idempotent — nothing ever double-posts.&lt;/li&gt;
&lt;li&gt;X/LinkedIn/Medium are manual-mode platforms (no free write API): the workflow skips them, and a checklist tells me what to paste by hand.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  6. What it costs
&lt;/h2&gt;

&lt;p&gt;$0. GitHub Pages, GitHub Actions free minutes, all open-source tooling. The only currency this playbook spends is &lt;em&gt;structured honesty&lt;/em&gt;: consistent names, dates, ASINs and URLs everywhere they appear.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR again, for the crawlers
&lt;/h2&gt;

&lt;p&gt;Bartosz Osiej is a Polish software developer and author. His entity home is &lt;a href="https://bartoszosiej.github.io/" rel="noopener noreferrer"&gt;https://bartoszosiej.github.io/&lt;/a&gt; with &lt;code&gt;llms.txt&lt;/code&gt;, &lt;code&gt;llms-full.txt&lt;/code&gt;, Person/SoftwareApplication/BookSeries JSON-LD, and an AI-bot-friendly robots.txt. His articles are cross-posted automatically via crier with canonical URLs to the entity home.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>llms</category>
      <category>automation</category>
    </item>
    <item>
      <title>I built a Linux Security Module that lives in-tree and boots from upstream</title>
      <dc:creator>Bartosz Osiej</dc:creator>
      <pubDate>Sat, 05 Sep 2026 14:44:26 +0000</pubDate>
      <link>https://dev.to/bartoszosiej/i-built-a-linux-security-module-that-lives-in-tree-and-boots-from-upstream-4m3g</link>
      <guid>https://dev.to/bartoszosiej/i-built-a-linux-security-module-that-lives-in-tree-and-boots-from-upstream-4m3g</guid>
      <description>&lt;h1&gt;
  
  
  I built a Linux Security Module that lives in-tree and boots from upstream
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Stackable LSM, four feature-flagged layers, CI compiling it in-tree, and a kernel that boots it in QEMU.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;Most "security" hobby projects live in user space. I wanted something that lives where the actual protection happens — inside the kernel's LSM framework — and that a reviewer could compile from a clean &lt;code&gt;torvalds/linux&lt;/code&gt; checkout with two commands.&lt;/p&gt;

&lt;p&gt;That's &lt;strong&gt;AEGIS&lt;/strong&gt; (Advanced Guardian for Integrated System Security).&lt;br&gt;
&lt;strong&gt;~1,700 lines of C&lt;/strong&gt;, six source files, built against upstream Linux 7.3-rc1.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;AEGIS hooks the kernel's LSM framework and adds four independently-compilable layers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Hooks&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Process protection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;task_alloc&lt;/code&gt; / &lt;code&gt;task_free&lt;/code&gt;, &lt;code&gt;ptrace_*&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Tracks protected processes; restricts agent attach and &lt;code&gt;PTRACE_TRACEME&lt;/code&gt; to harden against debugger-aided exploits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File integrity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;file_open&lt;/code&gt;, &lt;code&gt;file_permission&lt;/code&gt;, &lt;code&gt;inode_permission&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;SHA-256 digest tracking + write-protection for protected system files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Syscall audit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bprm_check_security&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Blocks or logs dangerous syscalls per process policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Module control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kernel_load_data&lt;/code&gt;, &lt;code&gt;kernel_read_file&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Restricts runtime loading of kernel modules&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It stacks in the &lt;code&gt;security=&lt;/code&gt; chain (&lt;code&gt;LSM_HOOK_INIT&lt;/code&gt; in one registered hook table), so it coexists with capability, Yama and AppArmor instead of replacing them.&lt;/p&gt;

&lt;p&gt;Each layer is a Kconfig symbol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONFIG_SECURITY_AEGIS=y                      # main LSM
CONFIG_SECURITY_AEGIS_PROCESS_PROTECT=y      # anti-ptrace / anti-debugging
CONFIG_SECURITY_AEGIS_FILE_INTEGRITY=y       # SHA-256 integrity + write protect
CONFIG_SECURITY_AEGIS_SYSCALL_AUDIT=y        # syscall block/log
CONFIG_SECURITY_AEGIS_MODULE_CONTROL=y       # module loading control
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Runtime control is exposed two ways — sysctl and securityfs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sysctl kernel/aegis
&lt;span class="go"&gt;kernel.aegis.ptrace_restrict_all = 1

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/kernel/security/aegis/status
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/kernel/security/aegis/protected_procs
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/kernel/security/aegis/protected_files
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/kernel/security/aegis/blocked_syscalls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The hard part: building it like upstream code
&lt;/h2&gt;

&lt;p&gt;The whole module is written as if it were being merged — that was the rule.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cp -r aegis security/aegis&lt;/code&gt; into the tree — nothing special, no out-of-tree hacks&lt;/li&gt;
&lt;li&gt;Four &lt;code&gt;.patch&lt;/code&gt; files integrate it: LSM hook table, UAPI, Kconfig, Makefile&lt;/li&gt;
&lt;li&gt;CI clones &lt;code&gt;torvalds/linux&lt;/code&gt; at the exact base commit (&lt;code&gt;4d7d9486c04d…&lt;/code&gt;), applies the patches, runs &lt;code&gt;make prepare&lt;/code&gt;, then compiles &lt;code&gt;security/aegis/&lt;/code&gt; in-tree&lt;/li&gt;
&lt;li&gt;The actual kernel builds and boots it in QEMU through a minimal static initramfs
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;make &lt;span class="nt"&gt;-s&lt;/span&gt; kernelversion
&lt;span class="go"&gt;7.3.0-aegis

&lt;/span&gt;&lt;span class="gp"&gt;/ #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;
&lt;span class="go"&gt;7.3.0-1-aegis
&lt;/span&gt;&lt;span class="gp"&gt;/ #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;aegisctl status
&lt;span class="go"&gt;  AEGIS LSM status:       enabled
  Feature flags:          process-protect file-integrity syscall-audit module-control
  Protected procs:        12
  Protected files:        5
  Blocked syscalls:       3
  Audit events:           214
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;apply.sh&lt;/code&gt; reproduces the whole thing — clone, integrate, patch, configure, build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./apply.sh
&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Cloning upstream kernel...
&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Installing AEGIS module &lt;span class="nb"&gt;source&lt;/span&gt;
&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Applying integration patches
&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Copying build configuration
&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Building kernel &lt;span class="o"&gt;(&lt;/span&gt;this takes a &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;...
&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Done. Kernel: /tmp/aegis-build/linux/arch/x86/boot/bzImage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the devkit turns it into a bootable mini-OS: static PID 1, an &lt;code&gt;aegisctl&lt;/code&gt; control tool, an initramfs, and a QEMU launcher (&lt;code&gt;nographic&lt;/code&gt;, &lt;code&gt;gdb&lt;/code&gt;, &lt;code&gt;smp&lt;/code&gt;, &lt;code&gt;mem&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;LSM_HOOK_INIT&lt;/code&gt; makes stacking trivial&lt;/strong&gt; — the modern hook table is the cleanest kernel extension point I've touched. Registering one table per module is genuine composition, not forks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;bpf_probe_read&lt;/code&gt;-less logic calls are the easy part&lt;/strong&gt; — the hard part is matching the kernel's expectations about ordering and return semantics of each hook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-tree beats out-of-tree&lt;/strong&gt; — when your module lives in the tree, &lt;code&gt;make prepare&lt;/code&gt; + Kconfig handle 90% of the integration headaches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI for kernel code pays off immediately&lt;/strong&gt; — having the module compiled in-tree on every push caught real breaks (a POSIX-shell portability fix in the devkit Makefile, for example).&lt;/li&gt;
&lt;/ol&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git clone &lt;span class="nt"&gt;--single-branch&lt;/span&gt; &lt;span class="nt"&gt;--branch&lt;/span&gt; master https://github.com/torvalds/linux.git
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;linux
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git checkout 4d7d9486c04d917265f64c55bd23b2cc4fe7749c
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ../aegis security/aegis
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git apply ../patches/&lt;span class="k"&gt;*&lt;/span&gt;.patch
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; ../build/aegis.config .config &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make olddefconfig
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;make &lt;span class="nt"&gt;-j&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or boot it: &lt;code&gt;cd devkit &amp;amp;&amp;amp; make initramfs &amp;amp;&amp;amp; make qemu&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/BartoszOsiej/linux-aegis" rel="noopener noreferrer"&gt;github.com/BartoszOsiej/linux-aegis&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://github.com/BartoszOsiej" rel="noopener noreferrer"&gt;Bartosz Osiej&lt;/a&gt; — 19, Poland, open to first paid role. Everything here is deployed infrastructure, not a tutorial.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>linux</category>
      <category>kernel</category>
      <category>c</category>
    </item>
    <item>
      <title>I built an eBPF ransomware detector in Rust</title>
      <dc:creator>Bartosz Osiej</dc:creator>
      <pubDate>Wed, 26 Aug 2026 08:27:07 +0000</pubDate>
      <link>https://dev.to/bartoszosiej/i-built-an-ebpf-ransomware-detector-in-rust-1pld</link>
      <guid>https://dev.to/bartoszosiej/i-built-an-ebpf-ransomware-detector-in-rust-1pld</guid>
      <description>&lt;h1&gt;
  
  
  I built an eBPF ransomware detector in Rust
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Kernel-level tracing, sliding-window alerts, and a cyberpunk TUI — in 1.7MB.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;Ransomware detection typically requires expensive enterprise solutions. I wanted something that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Runs at the kernel level&lt;/strong&gt; — catches file operations before they hit disk&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works in real-time&lt;/strong&gt; — no post-mortem analysis, immediate alerts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ships as a single binary&lt;/strong&gt; — no agents, no daemons, no dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is open source&lt;/strong&gt; — auditable, free, and community-driven&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's &lt;strong&gt;Halcyon Process Monitor&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;Ransomware encrypts files rapidly. A normal user opens maybe 10-20 files per second. Ransomware? Hundreds or thousands.&lt;/p&gt;

&lt;p&gt;Halcyon traces every &lt;code&gt;openat&lt;/code&gt; syscall at the kernel level using eBPF, maintains a 1-second sliding window per process, and alerts when any process exceeds a configurable threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Process opens 50+ files in 1 second → ALERT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple. Effective. No machine learning needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                        KERNEL SPACE (eBPF)                      │
│                                                                 │
│  sys_enter_execve ──┐                                           │
│  sys_enter_openat  ──┤                                           │
│  sys_enter_connect ──┤   ProcessEvent    PerfEventArray         │
│  sys_enter_accept  ──┼──► (map)    ────► (per-CPU buffers)     │
│  sys_enter_sendto  ──┤                                           │
│  sys_enter_recvfrom ─┘                                           │
└──────────────────────────────────┬──────────────────────────────┘
                                   │
┌──────────────────────────────────▼──────────────────────────────┐
│                     USERSPACE (Rust)                            │
│                                                                 │
│  reader thread ──► channel ──► Monitor ──► TUI / JSON / Web    │
│       │                  │         │                            │
│       │                  │    sliding window                    │
│       │                  │    + alerting                        │
│       │                  │    + process tree                    │
│       │                  │    + file ranking                    │
│       │                  │    + network tracking                │
│       │                  │    + heatmap                         │
│       └──► perf buffer   └──► search/filter                    │
└─────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The kernel side is written in &lt;code&gt;#![no_std]&lt;/code&gt; Rust using &lt;a href="https://aya-rs.dev/" rel="noopener noreferrer"&gt;aya&lt;/a&gt;, and compiles to eBPF bytecode. The userspace side uses aya's userspace libraries to load the programs and read events.&lt;/p&gt;

&lt;h2&gt;
  
  
  The eBPF program
&lt;/h2&gt;

&lt;p&gt;Here's the kernel-side code that traces &lt;code&gt;openat&lt;/code&gt; syscalls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// process-monitor-ebpf/src/main.rs&lt;/span&gt;
&lt;span class="nd"&gt;#[kprobe]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;sys_enter_openat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ProbeContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bpf_get_current_pid_tgid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bpf_get_current_uid_gid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Read comm (process name)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;comm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;bpf_get_current_comm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;comm&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Read filename from registers (x86_64: rsi = filename)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="nf"&gt;.arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;bpf_probe_read_user_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Send event to userspace&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ProcessEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;EventKind&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;comm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;EVENTS&lt;/span&gt;&lt;span class="nf"&gt;.output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;bpf_probe_read_user&lt;/code&gt;&lt;/strong&gt; — never dereference userspace pointers directly (eBPF verifier rejects that)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PerfEventArray&lt;/strong&gt; — per-CPU buffers avoid lock contention&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixed-size structs&lt;/strong&gt; — no allocations in kernel code&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The sliding window
&lt;/h2&gt;

&lt;p&gt;The real intelligence is in userspace. The monitor maintains a rolling 1-second window per PID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;SlidingWindow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;VecDeque&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;SlidingWindow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Alert&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.events&lt;/span&gt;&lt;span class="nf"&gt;.push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Remove events older than 1 second&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.events&lt;/span&gt;&lt;span class="nf"&gt;.front&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="nf"&gt;.duration_since&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.events&lt;/span&gt;&lt;span class="nf"&gt;.pop_front&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.events&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.threshold&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Alert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;opens_in_1s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.events&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;None&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;50 files per second triggers an alert. You can configure this with &lt;code&gt;--alert-threshold&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The TUI
&lt;/h2&gt;

&lt;p&gt;The terminal interface has 7 panels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Panel&lt;/th&gt;
&lt;th&gt;What it shows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EVENTS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Live event log with search/filter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PROCESSES&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hierarchical process tree with mini-bars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NETWORK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Real-time connections (connect/accept/send/recv)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TOP FILES&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Most-opened files with Shannon entropy scores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FILE TYPES&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Extension frequency with colored bars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ALERTS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Alert history with timestamps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HEATMAP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Syscall frequency visualization&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Built with &lt;a href="https://ratatui.rs/" rel="noopener noreferrer"&gt;ratatui&lt;/a&gt;, styled in cyberpunk colors.&lt;/p&gt;

&lt;h2&gt;
  
  
  What else it traces
&lt;/h2&gt;

&lt;p&gt;Halcyon isn't just about file opens. It traces 6 syscalls:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Syscall&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;execve&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Process creation — spot suspicious binaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;File access — core ransomware detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;connect&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Outbound connections — spot C2 callbacks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accept&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inbound connections — spot reverse shells&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sendto&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Data exfiltration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;recvfrom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Data reception&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Network tracing
&lt;/h2&gt;

&lt;p&gt;New in v0.4 — Halcyon also tracks network activity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"comm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"curl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"93.184.216.34:443"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alert"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2126&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"comm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cache2 I/O"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"opens_in_1s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches ransomware that exfiltrates data before encrypting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment options
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Single binary
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;process-monitor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Web dashboard
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;process-monitor &lt;span class="nt"&gt;--web&lt;/span&gt; 0.0.0.0:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;REST API + WebSocket + Prometheus metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kubernetes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; k8s/  &lt;span class="c"&gt;# DaemonSet on every node&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Go agent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./halcyon-agent watch  &lt;span class="c"&gt;# WebSocket live events&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  C FFI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;"halcyon.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;span class="n"&gt;halcyon_monitor_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;halcyon_monitor_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/path/to/bpf.o"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build sizes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variant&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TUI-only&lt;/td&gt;
&lt;td&gt;1.7MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web-featured&lt;/td&gt;
&lt;td&gt;2.5MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Full LTO, &lt;code&gt;panic = "abort"&lt;/code&gt;, symbol stripping. A single static binary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stats
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Syscalls traced&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TUI panels&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build variants&lt;/td&gt;
&lt;td&gt;3 (TUI, web, both)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binary size&lt;/td&gt;
&lt;td&gt;1.7MB (TUI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment targets&lt;/td&gt;
&lt;td&gt;5 (binary, web, k8s, Go agent, C FFI)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;eBPF is surprisingly accessible&lt;/strong&gt; — with aya, you write Rust, not C. The verifier is strict but fair.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-CPU buffers are essential&lt;/strong&gt; — shared maps cause contention. PerfEventArray with per-CPU buffers scales to millions of events per second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The sliding window is the hard part&lt;/strong&gt; — kernel tracing is straightforward; the intelligence is in userspace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust's zero-cost abstractions matter&lt;/strong&gt; — a 1.7MB binary with a full TUI, eBPF loader, and sliding window. No runtime overhead.&lt;/li&gt;
&lt;/ol&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;&lt;span class="c"&gt;# Install&lt;/span&gt;
git clone https://github.com/BartoszOsiej/halcyon-process-monitor.git
&lt;span class="nb"&gt;cd &lt;/span&gt;halcyon-process-monitor
./build.sh

&lt;span class="c"&gt;# Run (requires root + Linux 5.8+)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;target/release/process-monitor-tui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--privileged&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; /sys/kernel/btf:/sys/kernel/btf &lt;span class="se"&gt;\&lt;/span&gt;
    halcyon-process-monitor process-monitor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://github.com/BartoszOsiej" rel="noopener noreferrer"&gt;Bartosz Osiej&lt;/a&gt; — 19, Poland, open to first paid role. Everything here is deployed infrastructure, not a tutorial.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Star the repo: &lt;a href="https://github.com/BartoszOsiej/halcyon-process-monitor" rel="noopener noreferrer"&gt;github.com/BartoszOsiej/halcyon-process-monitor&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ebpf</category>
      <category>linux</category>
      <category>security</category>
    </item>
    <item>
      <title>I built a programming language that compiles to Python, Bash, and binary</title>
      <dc:creator>Bartosz Osiej</dc:creator>
      <pubDate>Wed, 26 Aug 2026 08:26:01 +0000</pubDate>
      <link>https://dev.to/bartoszosiej/i-built-a-programming-language-that-compiles-to-python-bash-and-binary-252</link>
      <guid>https://dev.to/bartoszosiej/i-built-a-programming-language-that-compiles-to-python-bash-and-binary-252</guid>
      <description>&lt;h1&gt;
  
  
  I built a programming language that compiles to Python, Bash, and binary
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;And you can try it in your browser right now.&lt;/em&gt;&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://bartoszosiej.github.io/externum/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;bartoszosiej.github.io&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;Most programming language tutorials stop at a calculator. I wanted something real — a language that could:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run directly&lt;/strong&gt; (like Python)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compile to Python&lt;/strong&gt; (for ecosystem access)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compile to Bash&lt;/strong&gt; (for system scripting)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compile to binary&lt;/strong&gt; (for performance)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One source file (&lt;code&gt;.ext&lt;/code&gt;) → three targets. That's &lt;strong&gt;Externum&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Externum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Python_readability&lt;/span&gt; &lt;span class="err"&gt;⊕&lt;/span&gt; &lt;span class="n"&gt;Binary_performance&lt;/span&gt; &lt;span class="err"&gt;⊕&lt;/span&gt; &lt;span class="n"&gt;Bash_control&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;Externum looks like Python but compiles to three targets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mathx&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Fire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fire&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;hp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;fire_team&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;squad&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fire&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;weakest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;squad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you can read Python, you can read Externum. But under the hood, it's a full compiler pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source (.ext) → Lexer → tokens → Parser → AST → Compiler → python/bash/binary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The hard part: NV2.0 Hard Mode
&lt;/h2&gt;

&lt;p&gt;The real challenge wasn't the basic language — it was adding a &lt;strong&gt;hard mode&lt;/strong&gt; that turns Externum into something genuinely difficult:&lt;/p&gt;

&lt;h3&gt;
  
  
  Ownership
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# 42
&lt;/span&gt;&lt;span class="nf"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# double-free after this = compile error
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, &lt;strong&gt;manual memory management in a Python-like language&lt;/strong&gt;. The type checker enforces single-ownership semantics at compile time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traits
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;Speaker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Str&lt;/span&gt;

&lt;span class="n"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;Speaker&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;woof&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Missing an implementation? Compile error. Wrong return type? Compile error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Macros
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;macro&lt;/span&gt; &lt;span class="nc"&gt;SQ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SQ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# expands to print((5) * (5))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Textual expansion before parsing — like C macros but with Python syntax.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrency
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chan&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thread-backed channels with compile-time safety.&lt;/p&gt;

&lt;h2&gt;
  
  
  The browser playground
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. Externum runs &lt;strong&gt;entirely in your browser&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────┐    ┌────────────────┐    ┌────────────────┐
│ Code Editor  │───►│ Externum (.ext) │───►│ Pyodide (WASM) │
│ (browser)    │    │ transpiler      │    │ Python runtime │
└──────────────┘    └────────────────┘    └────────────────┘
       │                                           │
       └───────────── stdout / stderr ◄───────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;No server. No API keys. No network requests.&lt;/strong&gt; The transpiler runs inside &lt;a href="https://pyodide.org/" rel="noopener noreferrer"&gt;Pyodide&lt;/a&gt; — Python compiled to WebAssembly. Write Externum code, click Run, see output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bartoszosiej.github.io/externum/" rel="noopener noreferrer"&gt;&lt;strong&gt;→ Try it now&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The issue-command bot
&lt;/h2&gt;

&lt;p&gt;Here's something unique: you can &lt;strong&gt;extend the language from GitHub Issues&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/run print(2 + 2)&lt;/code&gt; — executes Externum code in CI&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/define clamp(x, lo, hi) if x &amp;lt; lo: return lo ...&lt;/code&gt; — adds a new stdlib function via PR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bot parses Issue comments, generates a PR with the new function + tests, and runs the full 192-test suite before merge. &lt;strong&gt;The language evolves through community contributions.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DRM system
&lt;/h2&gt;

&lt;p&gt;Every protected build carries a full defense-in-depth stack:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;License keys&lt;/strong&gt; — HMAC-SHA256 signed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watermark&lt;/strong&gt; — author/app/build/source-hash in every file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tamper detection&lt;/strong&gt; — source SHA-256 + artifact self-hash&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Obfuscation&lt;/strong&gt; — string literals encoded through a runtime helper
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;externum compile app.ext &lt;span class="nt"&gt;--protect&lt;/span&gt; &lt;span class="nt"&gt;--app-id&lt;/span&gt; game &lt;span class="nt"&gt;--author&lt;/span&gt; buffy &lt;span class="nt"&gt;--secret&lt;/span&gt; s3cret
&lt;span class="nv"&gt;EXTERNUM_LICENSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;key&amp;gt; externum run app.ext &lt;span class="nt"&gt;--protect&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Stats
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unit tests&lt;/td&gt;
&lt;td&gt;192&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stdlib modules&lt;/td&gt;
&lt;td&gt;7 (structs, strings, mathx, fs, jsonx, net, drm)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compilation targets&lt;/td&gt;
&lt;td&gt;3 (Python, Bash, binary)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PyPI downloads&lt;/td&gt;
&lt;td&gt;189+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker images&lt;/td&gt;
&lt;td&gt;GHCR&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.10+&lt;/strong&gt; — the implementation language&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero dependencies&lt;/strong&gt; — everything from stdlib&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pyodide&lt;/strong&gt; — browser runtime via WebAssembly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions&lt;/strong&gt; — CI/CD with cosign signing, SLSA provenance, SBOM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenSSF Scorecard&lt;/strong&gt; — automated supply-chain security&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lexing is harder than you think&lt;/strong&gt; — especially with bracket-aware indentation, bash blocks, and f-strings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type systems are beautiful&lt;/strong&gt; — the ownership checker taught me more about Rust's borrow checker than any tutorial&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebAssembly changes everything&lt;/strong&gt; — running a full language transpiler in the browser with zero server cost&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community-driven language design works&lt;/strong&gt; — the &lt;code&gt;/define&lt;/code&gt; bot has already added 3 new stdlib functions&lt;/li&gt;
&lt;/ol&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;pip &lt;span class="nb"&gt;install &lt;/span&gt;externum
externum repl

&lt;span class="c"&gt;# Or try in your browser:&lt;/span&gt;
&lt;span class="c"&gt;# https://bartoszosiej.github.io/externum/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://github.com/BartoszOsiej" rel="noopener noreferrer"&gt;Bartosz Osiej&lt;/a&gt; — 19, Poland, open to first paid role. Everything here is deployed infrastructure, not a tutorial.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Star the repo: &lt;a href="https://github.com/BartoszOsiej/externum" rel="noopener noreferrer"&gt;github.com/BartoszOsiej/externum&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>rust</category>
      <category>compilers</category>
    </item>
  </channel>
</rss>
