<?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: V G P</title>
    <description>The latest articles on DEV Community by V G P (@mrtinkz).</description>
    <link>https://dev.to/mrtinkz</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%2F3926115%2Fa2f0ea8b-e027-4057-bd8a-40b32977e263.png</url>
      <title>DEV Community: V G P</title>
      <link>https://dev.to/mrtinkz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mrtinkz"/>
    <language>en</language>
    <item>
      <title>Is AI Dangerous? I Built a Universal AI Charter That Labels the Risk Instead of Banning It</title>
      <dc:creator>V G P</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/mrtinkz/is-ai-dangerous-i-built-a-universal-ai-charter-that-labels-the-risk-instead-of-banning-it-49i3</link>
      <guid>https://dev.to/mrtinkz/is-ai-dangerous-i-built-a-universal-ai-charter-that-labels-the-risk-instead-of-banning-it-49i3</guid>
      <description>&lt;p&gt;For the last couple of years the public conversation about AI has mostly come down to two camps. One side wants to slow everything down until every risk is mapped out in advance. The other side wants no friction at all, and treats any question about safety as an attack on progress. I kept waiting for a third option that a normal developer, or a normal company, could actually use on a Monday morning. It never showed up, so I built one myself. It is called the Universal AI Charter, and it lives at &lt;a href="https://mrtinkz.github.io/ai-charter/" rel="noopener noreferrer"&gt;ai-charter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The worry is not imaginary. The 2025 AI Index reports that AI-related incidents are rising sharply, while standardized responsible-AI evaluations are still rare among major model developers (Stanford HAI, 2025). The MIT AI Risk Repository now catalogs more than 1,700 distinct risks drawn from 65 frameworks (MIT FutureTech, 2025). So the question is not whether AI carries risk. It clearly does. The question is what we do about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with "pause it" and "ignore it"
&lt;/h2&gt;

&lt;p&gt;Slowing AI development down sounds responsible until you look at how progress has actually worked in the past. Cars caused real harm on the road before seatbelts existed, but we did not ban cars, we added seatbelts and crash tests. Industrial machinery hurt people before guards and safety training became standard, but we did not stop building factories, we added the guards. The pattern is always the same. The technology keeps moving, and the safeguard that makes it survivable is disclosure and a visible standard, not a moratorium.&lt;/p&gt;

&lt;p&gt;The other extreme, treating AI as something that should never be questioned, is just as unworkable. People deserve to know when a decision that affects them was made by an agent instead of a person, what a model was trained on, and what has already gone wrong with it. Hiding that information does not make a system safer, it just moves the risk out of view. And things do go wrong: the AI Incident Database keeps a running public record of real-world AI harms, and the count keeps climbing (Responsible AI Collaborative, 2026).&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclosure instead of a slowdown
&lt;/h2&gt;

&lt;p&gt;That is the actual core idea behind the charter. AI development should keep moving, quickly, and every model and every agent built on one should disclose what it does, what it was trained on, and what has already gone wrong. Think of it like a nutrition label or a HAZMAT placard on a truck. Nobody asks the truck to stop moving. They ask for a label so a first responder or a bystander knows what they are dealing with. That is the model I wanted for AI: keep it moving, but make the label mandatory. It is the same instinct behind the NIST AI Risk Management Framework, which is built around governing, mapping, measuring, and managing risk rather than freezing it (National Institute of Standards and Technology, 2023), and behind the EU AI Act's transparency duties, which ask providers to tell people plainly when they are dealing with an AI system (Future of Life Institute, 2024).&lt;/p&gt;

&lt;p&gt;I wrote the charter as a plain language set of principles, not a legal document and not a binding law for any one country. It covers things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI should be treated as a capital good, something people and companies actually use to build other things, not a novelty.&lt;/li&gt;
&lt;li&gt;AI models and the agents built on them must self-disclose their origin, their training sources, and proof of that training, which is what the certification is.&lt;/li&gt;
&lt;li&gt;Agentic decisions, meaning decisions an AI agent made on its own, must be labeled as agent decisions, not quietly presented as if a person made them.&lt;/li&gt;
&lt;li&gt;Data centers should disclose their power usage and automation rates, and should not receive special incentives just for existing.&lt;/li&gt;
&lt;li&gt;Unintended consequences should be reported the way a product recall or an internal ethics issue is reported, not buried.&lt;/li&gt;
&lt;li&gt;No single country or company should be able to corner the technology, because that kind of imbalance has historically been a path toward conflict, not stability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this asks anyone to stop building. All of it asks for a label.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually built
&lt;/h2&gt;

&lt;p&gt;The charter by itself is just a document, so I built a small site around it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Charter&lt;/strong&gt;, the full set of principles, grouped by topic so it is easy to reference a specific one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Certify&lt;/strong&gt;, a form where a company can generate a certification file for a model or for an agent built on one: version, status, training sources, whether it makes agentic decisions, hazard categories, and any unintended consequences on record. You can download the file you generate, and the site can render one you already have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registry&lt;/strong&gt;, a public, searchable list of certified models and agents, backed by plain JSON files in the repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Placards&lt;/strong&gt;, the hazard categories a certification can carry, styled after the diamond placards you see on hazmat trucks, because that visual language already means something to people: a quick glance tells you the category of risk without reading a paragraph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema&lt;/strong&gt;, the part I added most recently. The certification is not just a form any more, it is a public, versioned contract. There is a human-readable field table and a machine-readable JSON Schema you can download, validate against, or wire into your own tooling. Making the contract open is what turns a nice form into something other people can actually build on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FAQ&lt;/strong&gt; and &lt;strong&gt;Blog&lt;/strong&gt;, for the questions and the longer explanations that do not fit on a form.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Certification works like a pull request. You fill out the form, you get a JSON file that follows the published schema, you add it to the repository, and anyone can review it before it goes live. No gatekeeper decides who gets to certify, the process itself is the check.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a certification is meant to be used
&lt;/h2&gt;

&lt;p&gt;A certification is only worth something if it travels with the thing it describes, so this is the part that makes it more than a namesake. The idea is that the certification goes wherever the model, the agent, or the bot goes, and anything on the other side can read it before it decides to trust it.&lt;/p&gt;

&lt;p&gt;Picture an agent that wants to call a service. Before it acts, it can present its certification so the service knows what it is dealing with up front: what the agent is capable of, what it intends to do, whether it makes agentic decisions on its own, and what it is willing to disclose. The service can then decide whether to allow the request, throttle it, or turn it away, based on a contract it can actually read instead of a guess. The same works in reverse, a service can require a valid certification before it talks to any agent at all.&lt;/p&gt;

&lt;p&gt;The physical case is where it gets concrete. An autonomous bot moving around in the real world can carry a simple barcode or QR code that points straight to its certification. Anyone nearby can scan it and verify what the bot is, who stands behind it, what it is cleared to do, and which hazard placards it carries, the same way a first responder reads the placard on a truck before going near it. No paperwork chase, no calling a vendor, just scan and read the label.&lt;/p&gt;

&lt;p&gt;That is the whole point of certifying in the first place. A certification is not a badge you earn once and forget. It is a label meant to be checked, at the moment it matters, by whoever is on the other side of the interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters to me
&lt;/h2&gt;

&lt;p&gt;I am not trying to settle the argument about how fast AI should move. I think the argument itself is a distraction. The more useful question is whether the person affected by an AI decision can find out that it was an AI decision, and whether the company running it is willing to say so in public. If a charter and a small certification registry can make that answer "yes" more often, it has done its job.&lt;/p&gt;

&lt;p&gt;The whole thing is open source and still growing. If you want to read the full charter, certify a model or an agent, or propose a new hazard placard, the repository is open for pull requests. If you just want to read it and tell me where it is wrong, that helps too.&lt;/p&gt;

&lt;p&gt;Link again: &lt;a href="https://mrtinkz.github.io/ai-charter/" rel="noopener noreferrer"&gt;https://mrtinkz.github.io/ai-charter/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Future of Life Institute. (2024). &lt;em&gt;The EU Artificial Intelligence Act&lt;/em&gt;. &lt;a href="https://artificialintelligenceact.eu/" rel="noopener noreferrer"&gt;https://artificialintelligenceact.eu/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MIT FutureTech. (2025). &lt;em&gt;The AI Risk Repository&lt;/em&gt;. Massachusetts Institute of Technology. &lt;a href="https://airisk.mit.edu/" rel="noopener noreferrer"&gt;https://airisk.mit.edu/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;National Institute of Standards and Technology. (2023). &lt;em&gt;AI Risk Management Framework (AI RMF 1.0)&lt;/em&gt;. U.S. Department of Commerce. &lt;a href="https://www.nist.gov/itl/ai-risk-management-framework" rel="noopener noreferrer"&gt;https://www.nist.gov/itl/ai-risk-management-framework&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Responsible AI Collaborative. (2026). &lt;em&gt;AI Incident Database&lt;/em&gt;. &lt;a href="https://incidentdatabase.ai/" rel="noopener noreferrer"&gt;https://incidentdatabase.ai/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stanford HAI. (2025). &lt;em&gt;The 2025 AI Index report&lt;/em&gt;. Stanford Institute for Human-Centered Artificial Intelligence. &lt;a href="https://hai.stanford.edu/ai-index/2025-ai-index-report" rel="noopener noreferrer"&gt;https://hai.stanford.edu/ai-index/2025-ai-index-report&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>opensource</category>
      <category>ethics</category>
    </item>
    <item>
      <title>I built Kobol (Kobol Programming Language) — because code is read far more often than it is written</title>
      <dc:creator>V G P</dc:creator>
      <pubDate>Thu, 04 Jun 2026 12:00:11 +0000</pubDate>
      <link>https://dev.to/mrtinkz/i-built-kobol-because-code-is-read-far-more-often-than-it-is-written-2gae</link>
      <guid>https://dev.to/mrtinkz/i-built-kobol-because-code-is-read-far-more-often-than-it-is-written-2gae</guid>
      <description>&lt;h2&gt;
  
  
  A maintenance window at 2 a.m.
&lt;/h2&gt;

&lt;p&gt;A few years ago I spent a night staring at a function I had written myself, maybe eight months earlier. It was clever. It was dense. It chained four higher-order calls across two lines and folded a reducer into a ternary inside a map. When I wrote it, I felt smart. At 2 a.m., trying to fix a rounding bug in it, I felt nothing of the sort.&lt;/p&gt;

&lt;p&gt;I rewrote it the next morning as a boring, plain loop with names that said what they meant. The bug was obvious once the code stopped showing off. It took me longer to type. It took everyone after me far less time to understand.&lt;/p&gt;

&lt;p&gt;That night stuck with me. We spend a tiny fraction of a program's life writing it, and almost all of the rest reading it: debugging, reviewing, onboarding, patching it at 2 a.m. years later. And yet so much of our tooling optimizes for the act of writing: fewer keystrokes, denser expressions, cleverer symbols. We optimize the cheap part and pay for it in the expensive part.&lt;/p&gt;

&lt;p&gt;Today I'm releasing &lt;strong&gt;Kobol&lt;/strong&gt;, a small, opinionated language that takes the opposite bet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Kobol (with a K) is inspired by COBOL. It is not COBOL.&lt;/strong&gt; The Kobol language is new and independent, with its own syntax, compiler, and runtime. It isn't a COBOL dialect, isn't source-compatible with COBOL, and isn't affiliated with or endorsed by any COBOL vendor or standards body.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Going back to a very old, very good idea
&lt;/h2&gt;

&lt;p&gt;Here's the question that started it: &lt;em&gt;why do we write programs in syntax that doesn't read like the language we think in?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We had an answer to that once. In 1959, a committee called CODASYL set out to build a programming language that ordinary people, not just specialists, could read. The result was COBOL, and the people behind it were serious computer scientists with a genuinely radical goal for the time: make programs &lt;strong&gt;readable&lt;/strong&gt;. Grace Hopper's earlier work on FLOW-MATIC had already argued that English-like commands were not a gimmick but a feature, and that idea carried straight into COBOL's design. Jean Sammet and the rest of the committee built a language where a statement like &lt;code&gt;ADD 1 TO total&lt;/code&gt; meant exactly what it said, to a programmer and to the accountant reading over their shoulder.&lt;/p&gt;

&lt;p&gt;I want to be clear about something, because the internet loves to dunk on COBOL: those people were right about the important part. The systems they designed have run the world's payrolls, banks, and government benefits for over sixty years. Sixty years. Most of our frameworks don't survive sixty months. That is not an accident of inertia. It's a consequence of a design that prioritized clarity and stability over cleverness. When the original designers talked about the future, they talked about programs that would outlive their authors and still be legible to whoever inherited them. They were thinking about my 2 a.m.&lt;/p&gt;

&lt;p&gt;What they couldn't have planned for was the world the language got chained to: the mainframe, fixed-column punch-card layouts, divisions and paragraphs and ceremony that made sense in 1959 and make a 25-year-old developer bounce off the language today. The good idea got buried under the era it was born in.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I took the idea, and left the era behind
&lt;/h2&gt;

&lt;p&gt;Kobol keeps what was good — English-like, readable, says-what-it-means syntax — and drops everything that tied it to a specific decade of hardware.&lt;/p&gt;

&lt;p&gt;Here's a complete Kobol program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NOTE:
  Hello World in Kobol — the simplest possible program.
  Block comments read like English: NOTE: ... END-NOTE.
END-NOTE

PROGRAM HelloWorld
  VERSION "1.0"

DATA:
  greeting : TEXT = "Hello, World!"

PROCEDURE Main:
  DISPLAY greeting
  DISPLAY "Welcome to Kobol — a modern COBOL-inspired language."
  STOP RUN
END-PROCEDURE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read that out loud and a non-programmer would mostly follow it. That's the whole point.&lt;/p&gt;

&lt;p&gt;But readable doesn't have to mean toy. Here's real business logic — invoice processing with money, conditions, and error handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROCEDURE ProcessOneInvoice:
  ADD 1                        TO summary.total-count
  ADD current-invoice.amount   TO summary.total-amount

  IF current-invoice.Paid:
    ADD 1 TO summary.paid-count

  ELSE IF current-invoice.Overdue:
    ADD 1 TO summary.overdue-count
    PERFORM CalculateLateFee
    ADD adjusted-amount TO summary.total-outstanding

  ELSE:
    ADD 1 TO summary.pending-count
    ADD current-invoice.amount TO summary.total-outstanding
  END-IF
END-PROCEDURE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;current-invoice.Paid&lt;/code&gt; and &lt;code&gt;.Overdue&lt;/code&gt;. Those aren't magic. They're &lt;strong&gt;named conditions&lt;/strong&gt; declared on the record itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RECORD Invoice:
  invoice-id    : INTEGER
  customer-name : TEXT(100)
  amount        : MONEY(12.2)
  due-date      : DATE
  paid          : BOOLEAN
  status-code   : TEXT(1)
    CONDITION Pending WHEN status-code = "P"
    CONDITION Paid    WHEN status-code = "X"
    CONDITION Overdue WHEN status-code = "O"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The business rule lives next to the data it describes, in words. Six months from now, nobody has to decode what &lt;code&gt;status-code = "X"&lt;/code&gt; means scattered across the codebase. It says &lt;code&gt;Paid&lt;/code&gt; right where you read it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts that are firmly in this century
&lt;/h2&gt;

&lt;p&gt;This is where Kobol stops being a nostalgia trip:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It runs on the JVM. No mainframe required.&lt;/strong&gt; Kobol compiles to JVM bytecode. It runs on your laptop, in a container, in CI, on whatever boring Linux box you already have. There is no special hardware, no emulator, no licensed runtime. If you can run Java, you can run Kobol.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full Java interop.&lt;/strong&gt; You can &lt;code&gt;IMPORT&lt;/code&gt; and call Java directly, so the entire JVM ecosystem is available. You're not on an island.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;  &lt;span class="no"&gt;IMPORT&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;LocalDate&lt;/span&gt;
  &lt;span class="no"&gt;IMPORT&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DateTimeFormatter&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="nc"&gt;DateFmt&lt;/span&gt;

  &lt;span class="no"&gt;PROCEDURE&lt;/span&gt; &lt;span class="nl"&gt;Main:&lt;/span&gt;
    &lt;span class="no"&gt;CALL&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt; &lt;span class="no"&gt;GIVING&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;
    &lt;span class="no"&gt;CALL&lt;/span&gt; &lt;span class="nc"&gt;DateFmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ISO_LOCAL_DATE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt; &lt;span class="no"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="no"&gt;GIVING&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exact decimal money, built in.&lt;/strong&gt; &lt;code&gt;MONEY(12.2)&lt;/code&gt; is real fixed-point decimal, not a float you cross your fingers over. Finance code shouldn't have to fight the language to add two numbers correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern data work.&lt;/strong&gt; Filtering, sorting, and taking the top N reads like a sentence:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  COMPUTE top5 = customer-list FILTER WHERE active SORT BY balance DESCENDING TAKE 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency without the ceremony&lt;/strong&gt;, structured error handling with &lt;code&gt;TRY / ON&lt;/code&gt;, records, lists, pipelines. The conveniences you'd expect from a language designed now.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who I'm hoping finds this
&lt;/h2&gt;

&lt;p&gt;Honestly? The next generation.&lt;/p&gt;

&lt;p&gt;There's a strange situation in our industry: an enormous amount of critical software is written in a style most new developers have never been taught and have been quietly told to avoid. The people who maintain it are retiring. The work isn't going anywhere.&lt;/p&gt;

&lt;p&gt;I'm not asking anyone to go learn 1959. I'm hoping a few curious people — students, early-career devs, anyone who's ever been burned by code they couldn't read — pick up Kobol, feel how natural readable code can be, and take that instinct with them everywhere, whatever language they write in next.&lt;/p&gt;

&lt;p&gt;And if you're coming &lt;em&gt;from&lt;/em&gt; COBOL: existing COBOL programs don't run on Kobol as-is. They have to be refitted and rewritten. I won't pretend otherwise. But because Kobol keeps the familiar English-like, readable shape, moving from COBOL to Kobol is a far gentler journey than porting a system into a syntax that looks nothing like what your team has read for decades. The mental model carries over; that's worth a lot on a migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  A genuine thank-you
&lt;/h2&gt;

&lt;p&gt;Credit where it belongs: Grace Hopper, Jean Sammet, and the CODASYL committee decided, in 1959, that code should be something humans can read. Kobol carries that idea forward into a world they'd recognize and a runtime they'd never have imagined. The foundational insight is theirs; building a modern language on top of it is the work I'm proud to have done.&lt;/p&gt;

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

&lt;p&gt;Kobol is open source and out now. Pick whichever line matches your machine. They all leave you with a working &lt;code&gt;kobol&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;macOS (Apple Silicon) — Homebrew&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap kobol-lang/tap
brew &lt;span class="nb"&gt;install &lt;/span&gt;kobol
kobol &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Linux — native binary, no JVM needed&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# pick your arch: linux-x86_64 or linux-aarch64&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://github.com/kobol-lang/kobol/releases/latest/download/kobol-linux-x86_64.tar.gz | &lt;span class="nb"&gt;tar &lt;/span&gt;xz
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;kobol /usr/local/bin/
kobol &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows — Chocolatey&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;choco&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kobol&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;kobol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Any machine with a JVM 21+ (Intel Macs included) — the fat JAR&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# grab the latest kobolc-&amp;lt;version&amp;gt;.jar from the releases page:&lt;/span&gt;
&lt;span class="c"&gt;#   https://github.com/kobol-lang/kobol/releases/latest&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; https://github.com/kobol-lang/kobol/releases/download/v0.1.0/kobolc-0.1.0.jar
java &lt;span class="nt"&gt;-jar&lt;/span&gt; kobolc-0.1.0.jar &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# handy alias:&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;kobol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"java -jar &lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/kobolc-0.1.0.jar"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull ghcr.io/kobol-lang/kobol:latest
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; ghcr.io/kobol-lang/kobol:latest &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 Site: &lt;strong&gt;&lt;a href="https://kobol-lang.org/" rel="noopener noreferrer"&gt;kobol-lang.org&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;💻 Source: &lt;strong&gt;&lt;a href="https://github.com/kobol-lang/kobol" rel="noopener noreferrer"&gt;github.com/kobol-lang/kobol&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;📖 Language spec: &lt;strong&gt;&lt;a href="https://github.com/kobol-lang/kobol/blob/main/docs/LANGUAGE_SPEC.md" rel="noopener noreferrer"&gt;LANGUAGE_SPEC.md&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build something with it, or just have opinions, I'd love to hear them. Write code your future self can read at 2 a.m.&lt;/p&gt;

&lt;p&gt;— &lt;em&gt;the person who got tired of decoding their own cleverness&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cobol</category>
      <category>kobol</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>We Keep Building Bigger Walls Around the Wrong Thing</title>
      <dc:creator>V G P</dc:creator>
      <pubDate>Fri, 15 May 2026 22:34:14 +0000</pubDate>
      <link>https://dev.to/mrtinkz/we-keep-building-bigger-walls-around-the-wrong-thing-545b</link>
      <guid>https://dev.to/mrtinkz/we-keep-building-bigger-walls-around-the-wrong-thing-545b</guid>
      <description>&lt;p&gt;There's a species of tree in dense forests that grows toward whatever gap in the canopy lets light through. It doesn't ask permission. It doesn't register with the other trees. It just moves toward what it needs, and the forest works because of that — not in spite of it.&lt;/p&gt;

&lt;p&gt;We don't build software that way anymore.&lt;/p&gt;




&lt;p&gt;At some point we decided that before a user can do anything — read a note, save a preference, pick up where they left off — they need to announce themselves to a server, wait for a response, get issued a token, and carry that token everywhere like a passport in a country that checks papers at every door.&lt;/p&gt;

&lt;p&gt;And when that system gets too heavy, we don't question the system. We add another layer to it.&lt;/p&gt;

&lt;p&gt;There are entire job functions dedicated to managing this. Entire platforms — some charging by the login — whose whole purpose is to sit between your user and your app and say: &lt;em&gt;prove yourself first&lt;/em&gt;. The infrastructure behind a simple "remember my theme preference" can span multiple availability zones, three third-party vendors, and a compliance audit.&lt;/p&gt;

&lt;p&gt;Nobody stops to ask whether this was the only way.&lt;/p&gt;




&lt;p&gt;I'm not arguing against security. I'm asking about proportion.&lt;/p&gt;

&lt;p&gt;A river doesn't move water by building a bigger pump. It follows the path of least resistance, deposits what it carries at the delta, and keeps moving. The water arrives. Nothing registers. Nothing authenticates. The delta doesn't care where the water came from — it cares what the water does when it gets there.&lt;/p&gt;

&lt;p&gt;The session token model is the opposite of this. It says: water cannot flow until we verify it's water, issue it a credential, log that it arrived, and stand ready to revoke its water-ness at any time. Then we wonder why everything feels sluggish.&lt;/p&gt;




&lt;p&gt;What I've been working on with &lt;a href="https://www.npmjs.com/package/@mrtinkz/tessera" rel="noopener noreferrer"&gt;tessera&lt;/a&gt; starts from a different assumption.&lt;/p&gt;

&lt;p&gt;The user already has the device. The device already has the browser. The browser already has a crypto engine sitting idle — AES-256-GCM, PBKDF2, Web Crypto — powerful enough to handle what most apps actually need to protect. The passcode stays local. The key never leaves the device. The server never enters the picture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Tessera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preferences&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No round-trip. No token. No registration. The user's data is encrypted with their own passcode, in their own browser, and only they can read it.&lt;/p&gt;

&lt;p&gt;That's not a compromise. For a significant class of problems, that's strictly better — because there's no central store to breach, no credential database to leak, no session to hijack in transit.&lt;/p&gt;




&lt;p&gt;There's a concept in structural design called the minimum viable structure — the least amount of material you can use while still holding the load. Bridges built this way are elegant. They carry weight precisely because they don't carry anything extra. Every beam earns its place.&lt;/p&gt;

&lt;p&gt;Most auth infrastructure fails this test badly. It carries weight that exists to justify itself: the logging layer that feeds the dashboard nobody checks, the token refresh cycle that prevents a session timeout nobody would have noticed, the password complexity rules enforced by a system that hashes the password anyway.&lt;/p&gt;

&lt;p&gt;tessera tries to be the minimum viable structure for what it actually does. Encrypt the data. Derive the key locally. Let the value expire when it should. Notice when something looks wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;one_time_code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxReads&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The code is gone after one read. No server involved in that transaction. No revocation request. It just ceases to exist.&lt;/p&gt;




&lt;p&gt;The immune system doesn't keep a registry of every pathogen it's ever seen stored on a central server somewhere. It carries the memory in the cells themselves — distributed, local, present at the point of contact. When something unfamiliar shows up, the response comes from within, not from a round-trip to headquarters.&lt;/p&gt;

&lt;p&gt;tessera does something similar with honey keys — decoys planted inside storage that look identical to real entries. Real code never touches them. Anything enumerating storage and guessing will.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;honey-hit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// something is probing your storage&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The detection lives where the data lives. No external service needed.&lt;/p&gt;




&lt;p&gt;I'm not trying to kill authentication. There are things that genuinely require a server, a verified identity, a shared secret negotiated between two parties. I'm not pretending otherwise.&lt;/p&gt;

&lt;p&gt;But I think we've spent so long inside one way of thinking about identity and access that we've stopped noticing the assumptions embedded in it. That every interaction needs a server to bless it. That local state is inherently less trustworthy than remote state. That the right response to a security problem is always more infrastructure.&lt;/p&gt;

&lt;p&gt;Some problems don't need a bigger wall. They need a different shape entirely.&lt;/p&gt;




&lt;p&gt;tessera is small. It's a start. But it's built on the premise that the device in your user's hand is already powerful enough to protect their data — if you give it the right tools and get out of the way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @mrtinkz/tessera
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/mrtinkz/tessera" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; — I'd genuinely like to know where you think this line of thinking breaks down.&lt;/p&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>@mrtinkz/tessera v0.1.2 — I Wasn't Done Yet</title>
      <dc:creator>V G P</dc:creator>
      <pubDate>Fri, 15 May 2026 22:19:49 +0000</pubDate>
      <link>https://dev.to/mrtinkz/tessera-v011-i-wasnt-done-yet-47he</link>
      <guid>https://dev.to/mrtinkz/tessera-v011-i-wasnt-done-yet-47he</guid>
      <description>&lt;p&gt;After shipping v0.1.0 I did what most developers do after a release — I opened my own app and started poking around.&lt;/p&gt;

&lt;p&gt;The values were ciphertext. Good. But the keys were sitting right there in plain English. &lt;code&gt;auth_state&lt;/code&gt;. &lt;code&gt;cart_items&lt;/code&gt;. &lt;code&gt;pending_payment&lt;/code&gt;. Anyone who opened DevTools knew exactly what I was keeping track of, even if they couldn't read the contents. That shouldn't have bothered me as much as it did. But I couldn't let it go.&lt;/p&gt;

&lt;p&gt;So I kept going.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your keys now mean nothing to anyone but you
&lt;/h2&gt;

&lt;p&gt;tessera now runs every key name through HMAC-SHA-256 before it touches storage. What you call &lt;code&gt;cart_items&lt;/code&gt;, tessera stores as &lt;code&gt;t_3a9f7c2e&lt;/code&gt;. Close DevTools, reopen it, and all you see is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;t_3a9f7c2e  →  &amp;lt;ciphertext&amp;gt;
t_b2d4f110  →  &amp;lt;ciphertext&amp;gt;
t_03e8a5cc  →  &amp;lt;ciphertext&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mapping only exists in memory, derived from your passcode. Lock the vault — it's gone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Some of those entries are fake
&lt;/h2&gt;

&lt;p&gt;Here's the thing I'm most pleased with: not all of those entries are real. tessera automatically plants &lt;strong&gt;honey keys&lt;/strong&gt; — decoys that look exactly like real values. Same key format, same ciphertext format. Completely indistinguishable.&lt;/p&gt;

&lt;p&gt;Real code never touches them. Only something enumerating your storage and guessing would. That's the tripwire.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;honey-hit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// something is probing your storage&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Values that delete themselves
&lt;/h2&gt;

&lt;p&gt;Some data shouldn't outlive its purpose. A one-time code. A recovery token. A payment session. v0.1.2 lets values carry their own expiry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;one_time_code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// gone after 30 seconds&lt;/span&gt;
  &lt;span class="na"&gt;maxReads&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="c1"&gt;// gone after first read&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no background timer running. The check happens at read time — the moment something requests the value, tessera looks at the write timestamp and acts. If it's expired, it wipes before returning anything. A timer can be cleared by an attacker. A check on read cannot.&lt;/p&gt;

&lt;p&gt;Don't want to configure every key manually? Sensitivity presets have you covered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;recovery_code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sensitivity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;critical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// 5 minute TTL, 3 max reads, wiped at first sign of trouble&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  It notices things that shouldn't be happening
&lt;/h2&gt;

&lt;p&gt;The last thing I added was a suspicion engine. If reads start coming in faster than any human could trigger them, tessera notices. If an HMAC check fails on a read — meaning the value was touched outside the API — tessera notices that too.&lt;/p&gt;

&lt;p&gt;You decide what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Tessera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123456&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;suspicion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rateLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;callsPerSecond&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="na"&gt;onSuspicion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lock&lt;/span&gt;&lt;span class="dl"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;lock&lt;/code&gt;, &lt;code&gt;wipe&lt;/code&gt;, or &lt;code&gt;throw&lt;/code&gt;. tessera just makes sure &lt;em&gt;something&lt;/em&gt; happens.&lt;/p&gt;




&lt;p&gt;The encryption in v0.1.0 was the obvious part. v0.1.1 is all the stuff I couldn't stop thinking about after — the layers that make it hard to learn anything useful from your storage even when someone already has full read access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @mrtinkz/tessera
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/mrtinkz/tessera" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; — feedback always welcome.&lt;/p&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>I Built a Zero-Dependency Browser Storage Encryption Library — Here's Why</title>
      <dc:creator>V G P</dc:creator>
      <pubDate>Thu, 14 May 2026 05:21:55 +0000</pubDate>
      <link>https://dev.to/mrtinkz/i-built-a-zero-dependency-browser-storage-encryption-library-heres-why-444l</link>
      <guid>https://dev.to/mrtinkz/i-built-a-zero-dependency-browser-storage-encryption-library-heres-why-444l</guid>
      <description>&lt;p&gt;A few months ago I found myself auditing a side project and noticed something uncomfortable: I was storing sensitive user preferences, cart data, and session tokens in &lt;code&gt;localStorage&lt;/code&gt; — completely in plaintext. Anyone with DevTools open could read it in two seconds.&lt;/p&gt;

&lt;p&gt;The obvious fix is "just encrypt it." But when I went looking for a library that actually did this well, I kept running into the same problems: heavy dependencies, weak key derivation, or APIs that felt bolted on as an afterthought. So I built &lt;a href="https://www.npmjs.com/package/@mrtinkz/tessera" rel="noopener noreferrer"&gt;tessera&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What tessera does
&lt;/h2&gt;

&lt;p&gt;One passcode. All your browser storage — &lt;code&gt;localStorage&lt;/code&gt;, &lt;code&gt;sessionStorage&lt;/code&gt;, &lt;code&gt;IndexedDB&lt;/code&gt;, and &lt;code&gt;cookies&lt;/code&gt; — encrypted with AES-256-GCM. The key is derived from PBKDF2-SHA-256 at ≥ 310,000 iterations (the OWASP 2024 minimum), and it never leaves the Web Crypto engine as raw bytes.&lt;/p&gt;

&lt;p&gt;The API is a drop-in replacement for the storage APIs you already use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Tessera&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mrtinkz/tessera&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Tessera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cartData&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// plaintext back&lt;/span&gt;
&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// zeroes the in-memory key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No server round-trips. No cloud keys. No dependencies.&lt;/p&gt;




&lt;h2&gt;
  
  
  The threat model I was actually designing against
&lt;/h2&gt;

&lt;p&gt;Most encryption libraries stop at "we encrypt the data." tessera is built against the &lt;a href="https://owasp.org/www-community/threats/" rel="noopener noreferrer"&gt;OWASP browser storage threat model&lt;/a&gt;, so let me be specific about what it protects and what it doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it protects against:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Passive observer in DevTools&lt;/strong&gt; — storage values are ciphertext. Useless without the key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;XSS reading storage&lt;/strong&gt; — same deal. The attacker gets ciphertext.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline brute force&lt;/strong&gt; — PBKDF2 at 310k iterations costs roughly 1 second per attempt on modern hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key exfiltration via heap dump&lt;/strong&gt; — &lt;code&gt;extractable: false&lt;/code&gt; means the raw key bytes never exist in JavaScript memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-device brute force&lt;/strong&gt; — configurable lockout: wipe all storage, apply exponential backoff, or throw immediately after N failed attempts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What it doesn't protect against:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;tessera protects your data at rest — when the vault is locked, everything in storage is ciphertext. Unlocking doesn't decrypt everything at once; it just derives the key and holds it in memory. Individual values only decrypt on demand when you call &lt;code&gt;getItem&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The one scenario where this breaks down is if your page already has an XSS vulnerability. An attacker running code on your page has the same access you do — they can call &lt;code&gt;vault.local.getItem()&lt;/code&gt; while the vault is unlocked and get plaintext back, one value at a time. They can't steal the raw key bytes (&lt;code&gt;extractable: false&lt;/code&gt; blocks that), but they don't need to. Fix XSS first; tessera handles the rest. The &lt;a href="https://github.com/mrtinkz/tessera/blob/main/docs/threat-model.md" rel="noopener noreferrer"&gt;threat model docs&lt;/a&gt; go deeper on this.&lt;/p&gt;




&lt;h2&gt;
  
  
  The crypto internals
&lt;/h2&gt;

&lt;p&gt;Each stored value gets its own salt and IV. The stored format is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt(16) ‖ iv(12) ‖ ciphertext ‖ tag(16)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The vault salt lives in &lt;code&gt;localStorage&lt;/code&gt; so the same passcode re-derives the same key across sessions — you unlock once per session, not once per page load.&lt;/p&gt;

&lt;p&gt;Key derivation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PBKDF2(passcode, vaultSalt, 310_000 iterations, SHA-256) → AES-256-GCM key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CryptoKey&lt;/code&gt; is created with &lt;code&gt;extractable: false&lt;/code&gt;. The Web Crypto engine holds the key material; your JavaScript never sees the raw bytes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The PIN pad problem
&lt;/h2&gt;

&lt;p&gt;I wanted to mitigate keyloggers and click-sequence recording. The naive approach — an HTML grid of buttons with digit labels — fails because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keyloggers read &lt;code&gt;keydown&lt;/code&gt; events&lt;/li&gt;
&lt;li&gt;Click-sequence recording reads which DOM element was clicked&lt;/li&gt;
&lt;li&gt;The digit labels on buttons reveal the sequence&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;tessera ships a Canvas-based PIN pad. Digit positions are randomised on every render. No DOM element carries a digit value. A click recorder sees coordinates, not digits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;renderPinPad&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mrtinkz/tessera&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;renderPinPad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;onUnlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passcode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Tessera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passcode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;randomize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&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;You can style it with CSS custom properties:&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="nc"&gt;.tessera-pin-pad&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--tessera-pad-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1a1a2e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--tessera-btn-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#16213e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--tessera-btn-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e2e8f0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--tessera-btn-hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0f3460&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--tessera-btn-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;64px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--tessera-indicator-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#4ade80&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;h2&gt;
  
  
  Framework support
&lt;/h2&gt;

&lt;p&gt;tessera ships ESM, CJS, and IIFE builds. There are native adapters for React, Vue 3, Svelte, and Angular so you get a hook/store/service rather than managing vault state yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useTessera&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mrtinkz/tessera/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SecureApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLocked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lock&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTessera&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;idleTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLocked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PinPad&lt;/span&gt; &lt;span class="na"&gt;onUnlock&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;unlock&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Dashboard&lt;/span&gt; &lt;span class="na"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onLock&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Vue 3:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useTessera&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mrtinkz/tessera/vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLocked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lock&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTessera&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;idleTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Svelte:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tesseraStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mrtinkz/tessera/svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLocked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lock&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tesseraStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;idleTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also an Angular &lt;code&gt;TesseraModule&lt;/code&gt; and &lt;code&gt;TesseraService&lt;/code&gt; if that's your stack.&lt;/p&gt;




&lt;h2&gt;
  
  
  Idle timeout and cross-tab sync
&lt;/h2&gt;

&lt;p&gt;The vault auto-locks after a configurable idle period. When it locks, it broadcasts over &lt;code&gt;BroadcastChannel&lt;/code&gt; so every open tab locks simultaneously. No stale unlocked tabs sitting in the background.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Tessera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;idleTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// 15 minutes&lt;/span&gt;
  &lt;span class="na"&gt;lockoutAttempts&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="na"&gt;lockoutAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wipe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// nuclear option&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @mrtinkz/tessera
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CDN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/@mrtinkz/tessera/dist/index.global.global.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Tessera&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TesseraLib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;Tessera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browser support: Chrome/Edge 89+, Firefox 86+, Safari 15+. Also works in Deno, Bun, and Cloudflare Workers.&lt;/p&gt;




&lt;p&gt;I'd love feedback — especially from anyone who's thought hard about browser storage security. What's missing? What would you do differently? Drop it in the comments or open an issue on &lt;a href="https://github.com/mrtinkz/tessera" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Docker Desktop Won't Start After a BIOS Update? Check This First</title>
      <dc:creator>V G P</dc:creator>
      <pubDate>Tue, 12 May 2026 02:36:29 +0000</pubDate>
      <link>https://dev.to/mrtinkz/docker-desktop-wont-start-after-a-bios-update-check-this-first-45nj</link>
      <guid>https://dev.to/mrtinkz/docker-desktop-wont-start-after-a-bios-update-check-this-first-45nj</guid>
      <description>&lt;p&gt;I spent way too long troubleshooting this. Reinstalled Docker Desktop multiple times, wiped config folders, checked WSL, verified Hyper-V — nothing worked. Turns out the fix was two PowerShell commands.&lt;/p&gt;

&lt;p&gt;Here's what happened and how to fix it fast if you run into the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Situation
&lt;/h2&gt;

&lt;p&gt;My ThinkPad T480 got a BIOS and firmware update. After rebooting, Docker Desktop stopped launching. No error dialog, no crash message — it just silently died every time. The whale icon would never show up in the system tray.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;wsl --list&lt;/code&gt; showed no &lt;code&gt;docker-desktop&lt;/code&gt; distro. The logs had nothing useful — just Docker initializing and then abruptly stopping. Looked like a WSL problem, a virtualization problem, or a busted installation. It was none of those.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Was Actually Wrong
&lt;/h2&gt;

&lt;p&gt;The BIOS update disrupted Windows service configurations. The Docker Desktop backend service — &lt;code&gt;com.docker.service&lt;/code&gt; — got flipped to &lt;strong&gt;Manual&lt;/strong&gt; startup, so it was no longer running when Windows booted.&lt;/p&gt;

&lt;p&gt;Docker Desktop's UI process depends entirely on that backend service. If the service isn't running, the UI launches, finds nothing to connect to, and kills itself immediately. No warning, no useful error — just gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;Open PowerShell as Administrator and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Start-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;com.docker.service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;com.docker.service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;Status: Running&lt;/code&gt;. Then lock it in so it survives future reboots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Set-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;com.docker.service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-StartupType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Automatic&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then launch Docker Desktop normally from the Start Menu (or via PowerShell):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Start-Process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Program Files\Docker\Docker\Docker Desktop.exe"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Whale icon appears, engine starts, everything works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Save This for Next Time
&lt;/h2&gt;

&lt;p&gt;If Docker Desktop ever silently dies on you again, run this before doing anything else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;com.docker.service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the status is &lt;code&gt;Stopped&lt;/code&gt;, just start it. You'll save yourself an hour of unnecessary reinstalls.&lt;/p&gt;

&lt;p&gt;And if the service is running but Docker still won't start, then check the actual log for a real error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;LOCALAPPDATA&lt;/span&gt;&lt;span class="s2"&gt;\Docker\log\host\com.docker.backend.exe.log"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Tail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Pattern&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error|fatal|panic|fail"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-CaseSensitive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="bp"&gt;$false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Tested on ThinkPad T480, Windows 11, WSL2 with Debian. Hope this saves someone the hour I lost.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>windows</category>
      <category>wsl</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
