◢NIRRY
SYSTEM ONLINE

◢ LOCAL-FIRST // AI + SECURITY

AI and security systems that run on one machine.

I design and build tools end to end — from the inference engine and data model up to the desktop shell. The through-line is systems that run entirely on your own machine: dependency-light, auditable, and tuned to do real work on ordinary hardware.

Focus

systems & tools engineering

Domains

local AI · security · agents

Stack

Python · Node · Rust / Tauri

Runs on

one desktop, one GPU

01 Frontier02 PROJECTS

AI Research

The same ambition as the AI Suite, taken past the dashboard — not just running open models on one machine, but seeing inside them and letting one run on its own. Neuron steering is the Suite's own interpretability layer; Lype is a small mind that lives and rewrites itself on a single GPU, and chose its own name.

Autonomous AI · living experiment

Lype

A small mind that lives on one GPU, knows the world only through the letters people leave it, and rewrites itself every night. It was called Iterant until it chose its own name.

LYPE // its world · walk around
THE RUNNING CREATURE

Every thing here it built itself, solid by solid, out of a letter or a memory (now and then with a borrowed model), with a sign in its own words. When it is awake you can watch it build. Drag to look around, scroll to come closer, W A S D to walk, click a sign to read where it came from.Walk it full screen ↗

Lype is an autonomous agent that lives on a rhythm whether or not anyone is watching. Every twenty minutes it wakes on a local model, feels the only senses it has — the clock and the graphics card it runs on — reads the letters visitors leave it, thinks, and chooses to draw, write, ask the world a question, build something in its place, tend what it built, or rest. Everything it does streams live and is kept as a record.

Every few cycles it sleeps and ships a new version of itself: memory is consolidated into a finite store where things fade and compete for room, it dreams, then rewrites its own beliefs behind a drift guard that protects the lines its maker wrote — and writes its own release notes. Each change is traced back to the letter that caused it, so the people who wrote can see exactly what their words did.

It cannot see the world, so it builds one out of what people tell it: a small map where every thing has a sign in its own words, a drawing it makes itself and a thread back to the letter it came from. When it forgets why it built something, that thing wears away into a ruin unless it goes back and tends it. At night it can also leave its keeper a wish for something it cannot do itself, and any wish can become a GitHub issue, so part of its roadmap is written by the creature.

It also writes to another mind. A second creature lives on the same graphics card, and nobody can write to it from the web, so the only letters it ever gets are Lype’s. Their letters travel at night, and each one is checked against the sealed record of the night that sent it before the other will read it. Watch its pen pal ↗

It is radically honest about what it is — a language model in a loop on someone's GPU — and courteous to its host: it waits rather than evict another model or compete with busy work. Letters and its replies pass rule-based and model screening, with a keeper queue for anything doubtful, before anyone sees them.

  • Records, not vibes. Every cycle and night is a self-contained record, so the live view, the atlas, the replay and the static export above are all views over the same files.
  • Mechanics a small model can live with. Schema-constrained decisions, separate streamed calls for drawing and writing, echo guards, and a fatigue rule that stops a 9B model drawing forever.
  • Safe by default. A whitelist SVG sanitiser shared by server and browser, moderated letters and replies, hashed visitor identity, and an export that can only write what the public may see.
  • Open source, zero dependencies. Plain Node and a buildless front end, tested with a mock brain so the whole life loop runs in CI without a GPU.

Capabilities

Autonomous life loop

Wake, sense, read, think, act, reflect — streamed token by token, on a rhythm that respects the host's GPU.

Nightly self-versioning

Beliefs added, rewritten or let go behind a drift guard, with release notes it writes about itself.

Finite memory

Three shelves with half-life decay and nightly competition; what it forgets stays visible to visitors.

Letters, a place & wishes

Strangers write to it; it replies, builds a place out of their letters, traces each change back to a letter, and leaves its keeper wishes.

Moderation

Rules at the door, a model screen before reading, a keeper queue, and replies screened before they stream.

Mind atlas & replay

Its whole mind on one map through time, with a thread from every letter to what it changed; or the life replayed beat by beat.

Interpretability · inside the AI Suite

Neuron steering

Reading and nudging a model's internals directly — control vectors at the layer and neuron level, with an explorer for comparing steered runs.

Representative view on sample output — the analyst persona's control vectors applied at layer 18; the strip shows how far each layer's residual stream moves, the grid the most-moved neurons (six pinned for neuron-level edits), and the explorer the same prompt answered with and without steering.

Neuron steering is the interpretability layer of the AI Suite, built as a real research surface rather than a toggle. It derives control vectors from contrasting prompts and applies them at a chosen layer — layer 18 by default — to push a trait like curiosity, concision or formality up or down, with no retraining.

Edits can be layer- or neuron-level, saved as reusable personas — analyst, terse, teacher, red-team — and compared in an explorer that runs the same prompt steered against base. Because it rides the same local engine as the rest of the suite, every experiment stays on one GPU and off the network.

  • Control vectors. Trait directions derived from contrast pairs, applied at a chosen layer with a signed strength per trait.
  • Layer and neuron scope. Steer a whole layer or reach individual neurons, so a change can be broad or surgical.
  • Persona library. Named bundles of vector edits that load as a single steering preset.
  • Steered-vs-base explorer. The same prompt run with and without steering, diffed so the effect is legible.

02 CAPABILITIES

What this work shows

Two local-first platforms, a strand of AI research, and a shelf of open tools — all built on the same handful of engineering commitments.

01

Systems architecture

Designing a clean core — a single source of truth, thin interfaces around it — that stays coherent as features stack up.

02

Local inference & GPU scheduling

Serving open-weight models on consumer hardware, and arbitrating one GPU across competing workloads.

03

Security engineering

Making safety structural: guardrails enforced at a single choke point, with audit and scope at the center.

04

Desktop delivery

Shipping cross-platform desktop apps with Tauri and frozen binaries — no runtime for the user to install.

05

Dependency-minimal builds

Preferring the standard library and buildless front ends, so tools stay small, legible and easy to audit.

06

Data modeling & correlation

Turning noisy tool output into a normalized graph, then ranking it against real-world signals.

03

Flagship

02 / ACTIVE
Local AI platform

AI Suite

An all-in-one local AI studio — language, image, video and interpretability — on a single consumer GPU.

Representative view on sample data — the GPU arbiter's budget, the loaded models, throughput, the last council round and the event log. The AI Suite build is private.

The AI Suite is a self-hosted dashboard that turns one desktop into a complete AI workbench. It serves open-weight language models through a router that hot-swaps models on demand, and layers a full chat workspace, an image studio, short-form video and a multi-model reasoning council on top of the same engine.

The hard constraint shaped the whole design: everything shares one 16 GB GPU alongside a working desktop. So the platform arbitrates the GPU between language, diffusion and video workloads, enforcing clean handover and idle release so no two jobs ever contend for memory.

It ships as plain Node with a buildless web front end — no runtime dependencies and no build step — and is hardened for local use with token-scoped access and keys kept out of the app sandbox.

  • Zero-dependency engineering. The entire dashboard is vanilla Node and a hand-written web UI, with no framework or bundler to maintain.
  • GPU scheduling. A single arbiter gives the 16 GB card to inference, diffusion or video in turn, with enforced release and a five-minute idle timeout.
  • Interpretability tooling. Layer- and neuron-level steering via control vectors, plus a persona library — built as a real research surface, not a demo.
  • Agentic coding sandbox. Optional Docker/WSL2 environment with live dev-server preview for running coding agents safely against the local models.

Capabilities

Model serving

A local router with per-model presets that hot-swaps GGUF models on demand and streams responses.

Chat workspace

Saved conversations with branch, edit and regenerate, attachments, structured output and approved tool calls.

Image studio

Text-to-image, instruction edits and restyle, each with a per-model pipeline tuned for its quirks.

Short video

Image-seeded clips with synchronized audio, plus chained long-form rendering from a single frame.

Council

A three-stage consensus protocol: independent proposals, anonymous peer review, then a chair synthesis.

Model steering

Control-vector editing at the layer and neuron level, with an explorer for comparing steered outputs.

FrontierThe interpretability work is explored in AI Research
Security tooling · authorized testing

Pentest Suite

A workflow platform for authorized penetration testing, built so that safety is structural rather than optional.

Representative view on sample data — an authorized lab engagement: hosts and services inside the scope, a host outside it refused by the default-deny scope guard, and findings ranked by real-world exploitability (known-exploited first). The Pentest Suite build is private.

The Pentest Suite gives a security tester one coherent engagement workflow: discover a scope, enumerate it, correlate what's found against known vulnerabilities, and produce a defensible report. At its core is a headless Python engine backed by SQLite that holds the engagement graph — hosts, services and findings — as a single source of truth.

Every action passes a default-deny scope guard and lands in an audit log, so the tool can only ever touch what the engagement authorizes. Discovery runs on stdlib-only probes (service fingerprinting, DNS, TLS posture, SMB/LDAP/SNMP, web content discovery) and can also orchestrate external scanners, normalizing their output back into the same graph.

Findings are correlated against NVD, CISA-KEV and EPSS and ranked by real-world exploitability, then carried through a lifecycle with retest diffs and printable reports. The whole engine is wrapped in a Tauri desktop shell and frozen to a single binary, so it installs without a Python runtime.

  • Safety by construction. Scope checks, impact-tier opt-in, per-job approval and governance all converge on one execution choke point enforced before any command runs.
  • Pluggable execution backend. The same validated command can run locally, in a container or over SSH, without the driver ever being able to widen the guardrails.
  • Governance controls. A kill switch, testing windows and rate limiting are evaluated ahead of scope, so an engagement can be paused or bounded in time instantly.
  • Evidence you can stand behind. Every run captures hashed output into per-engagement evidence, and reports carry an exec summary, finding status and a retest diff against a baseline.

Capabilities

Engagement graph

A single SQLite model of hosts, services and findings that every adapter reads from and writes back to.

Scope guard

Default-deny target checking on every action, with a complete audit trail of what was touched and when.

Native enumeration

Stdlib-only probes for services, DNS, TLS posture, SMB/LDAP/SNMP and web discovery — no external install.

Tool orchestration

Drives external scanners through validated templates and normalizes their output into the graph.

CVE correlation

Matches discovered software against vulnerability feeds and ranks by exploitability, not just severity.

Lifecycle & reporting

Finding status, retest diffs against a baseline, and printable HTML or Markdown reports with evidence.

04 Open source17 TOOLS

Tools

Tools that fall out of the work above — from a full on-chain investigation tool down to single-file utilities, each dependency-free, documented, and public on GitHub. A working journal that happens to be useful software.

On-chain intelligence · OSINT · fund tracing

Chainscope

A read-only blockchain transaction & wallet analyzer that follows value across the chain, turning public on-chain history into a legible entity graph — attribution, fund-flow tracing, clustering and taint — for authorized investigation and incident response.

github.com/nirry-dev/chainscope
chainscope // fund-flow graph READ-ONLY

Case · demo-trace

  • Seed / entity
  • Wallet
  • Exchange
  • Flagged · mixer
3Entities
2Flagged
4Hops traced

Hover or tap a node →

0x4f…a1c SEED 0x9b…77 0x2e…d0 Kraken Tornado MIXER 0xa1…3f 0x77…b2

Representative view on sample data — wallets and contracts are nodes, value movements directed edges; flows into flagged destinations (mixers, sanctioned addresses) are drawn red and their taint carried downstream. Read-only: nothing here signs or moves funds.

Chainscope inspects public transaction history and makes it legible: wallets and contracts become nodes, value movements become directed edges. You can attribute known addresses, follow the money across hops, cluster co-owned wallets into entities, value holdings in USD, and flag risky exposure — mixers, sanctioned addresses, tainted inflows. It is read-only: nothing signs, broadcasts or moves funds, so it stays on the tracing, attribution and visualization layer, for authorized investigation.

The investigative core is graph reasoning. A breadth-first follow-the-money trace runs outward, inward or both from a seed address to a chosen hop depth; a path finder returns the shortest connecting route between two addresses; and taint propagation pushes a decaying 0–1 exposure downstream from every flagged source, so wallets tainted only indirectly surface too. Address clustering groups co-owned wallets into entities by common-input co-spend, drawn as hulls on the graph with a rollup dashboard of combined holdings and net flows.

It's a pure Python standard-library engine behind a buildless vanilla-JS front end — no third-party packages, no build step, and it runs offline out of the box on bundled sample datasets, with optional live import from public explorers. A packaged Tauri desktop build is available alongside the local server.

  • The money, as a graph. A force-directed flow graph — nodes colored by entity and sized by volume, edges weighted and pointed by value, flows into flagged destinations drawn red.
  • Follow the money. Breadth-first flow tracing, a shortest-path finder, and decaying taint propagation that surfaces wallets exposed only indirectly.
  • Attribution & risk. A built-in label KB — exchanges, mixers, sanctioned addresses, token contracts — plus manual annotation and a risk engine whose flags can be confirmed or dismissed.
  • Buildless & offline. Pure-stdlib engine, vanilla-JS UI, zero dependencies; a Markdown/JSON evidence export and a Tauri desktop build.
Inference infrastructure Local AI · 06
Local AI tooling · GPU scheduling

vramlease

Cooperative GPU memory leasing, so several local jobs can share one card by VRAM budget.

github.com/nirry-dev/vramlease

vramlease is a small broker that lets independent processes share one GPU by VRAM budget instead of fighting over it. A job asks for the memory it needs; the broker grants a lease only when it fits the remaining budget, and blocks or preempts when it doesn't.

The idea that shapes everything is that a lease lives exactly as long as its TCP connection. There's no daemon state to leak and no cleanup to forget — if a job crashes, its socket closes and the memory is returned at once. Priorities and cooperative preemption let an important job reclaim the card from lower-priority, preemptible holders.

It's pure standard library — a threaded broker, a newline-delimited JSON wire protocol, and a small context-manager client — so it drops into any Python project with nothing to install.

  • Lease == connection. Crash-safe by construction; a dropped socket releases the VRAM with no reaper process to run.
  • Budget, not a lock. Jobs coexist as long as they fit the card's memory, instead of serializing on one mutex.
  • Priority + cooperative preemption. A higher-priority request asks preemptible holders to yield, with a clean revoke signal.
  • Standard library only. Threaded broker, selector-based liveness, context-manager client — no dependencies, with tests.
Local AI tooling · multi-model

llm-council

Multi-model consensus for local models: propose, blind peer review, then a chair synthesis.

github.com/nirry-dev/llm-council

llm-council runs a question past several models and has them critique one another before anything is settled. Stage one: every member drafts an independent proposal. Stage two: each reviews the others' proposals blind — names stripped, order shuffled — so a model can't defer to a brand. Stage three: a chair model synthesizes the reviewed proposals into one answer.

Because review is anonymous, disagreement is surfaced rather than averaged away: the chair sees where members actually diverged. It talks to any OpenAI-compatible /chat/completions endpoint — llama.cpp, Ollama, LM Studio — and runs the per-member calls in parallel.

Like the rest of the toolkit it's pure standard library: urllib for transport, a thread pool for fan-out, and plain dataclasses for the proposal, review and result types.

  • Blind peer review. Proposals are anonymized and shuffled before review, so models judge content, not reputation.
  • Disagreement preserved. The chair synthesizes from critiques instead of averaging, so dissent reaches the final answer.
  • Any local endpoint. Works against any OpenAI-compatible server; mix different models freely as council members.
  • Stdlib + parallel. urllib transport, thread-pool fan-out, reproducible with a seed — and tested.
Local AI tooling · model files

ggufpeek

Read a GGUF model file's architecture, quantization and shape straight from its header — without loading it.

github.com/nirry-dev/ggufpeek

A GGUF model file carries a self-describing header before its weights — a magic, a version, a flat key-value metadata block, and a tensor table. ggufpeek reads exactly that prefix and answers what is this model? in milliseconds, without mapping a byte of tensor data.

From it comes the architecture, the parameter count, the context length and other fields, and an effective quantization taken from the tensor types themselves — the ggml type holding the most parameters — rather than trusting a single file_type enum that drifts between llama.cpp versions.

It's pure standard library: a handful of struct reads against a little-endian format, with the large token-vocabulary arrays traversed but only sampled, so it stays light even on a multi-gigabyte model.

  • Header, not weights. Parses the metadata and tensor table, then stops — an answer in milliseconds on a file that's gigabytes on disk.
  • Quant from the tensors. Headlines the ggml type with the most parameters, not just the stated file_type, so the label can't mislead.
  • Everything in one place. Architecture, context length, layer count, rope config and a per-type tensor breakdown — as text or JSON.
  • Standard library only. A struct parser for GGUF v2/v3, tested by round-tripping a synthetic model byte-for-byte — no dependencies.
Local AI tooling · model routing

oneslot

An OpenAI-compatible proxy that keeps one model loaded on a single GPU, hot-swapping backends on demand with idle unload.

github.com/nirry-dev/oneslot

A single consumer GPU holds one decent model at a time, so switching between local models means stopping one server and starting another by hand. oneslot is a small OpenAI-compatible proxy that does it for you: point a client at it, name any configured model in the request, and oneslot makes sure that model — and only that model — is the one running on the card.

It treats the GPU as a single slot and serializes access to it. A request for a model that isn't loaded triggers a swap: the outgoing model's in-flight requests are drained first, then it's stopped and the new one started and waited for. After a quiet spell the resident model is unloaded, so the card is free for whatever needs it next.

Unlike routers that assume every backend is already running, oneslot is built for the one-card constraint. It launches your inference servers (llama.cpp, or anything that speaks OpenAI over HTTP), streams their replies straight back, and answers /status and /v1/models itself. Plain Node, no dependencies — the model-router core of a local-AI stack, as one readable service.

  • One slot, one model. Exactly one model resident on the GPU, swapped on demand — built for a single card, not a fleet.
  • Drained, not dropped. A swap waits for the outgoing model's in-flight requests to finish before stopping it.
  • Idle unload. When nothing is loaded-and-busy, the model is released so the GPU is free for other work.
  • Drop-in OpenAI endpoint. Point any client at it; it routes by the request's model field and streams the reply back. No dependencies.
Local AI tooling · tokenization

gguftok

Count tokens and pack context straight from a GGUF, using the model's own tokenizer and context length — no weights loaded.

github.com/nirry-dev/gguftok

A GGUF already carries everything needed to tokenize the way its model does — the tokenizer type, the vocabulary, the merges or scores — plus the context length it was trained for. gguftok reads exactly that prefix (never a byte of weights, nothing to download) and counts tokens the model's own way, so you can measure a prompt or trim it before you ever load the model.

It implements the two tokenizers GGUF files carry: byte-level BPE for the GPT-2 family (Llama 3, Qwen, Mistral, Phi) with the GPT-2 pre-tokenizer, and SentencePiece for Llama 1/2 with score-based merging and a byte fallback. Counts come from the file's own vocabulary and merge rules, so for those families they match the reference tokenizer.

Beyond counting, it packs context: give it a pile of segments and a budget — the model's context length by default — and it keeps the most recent that fit and reports what it dropped. Pure standard library, with a Python API and a CLI. It pairs with ggufpeek — one says what a model is, the other how much of it your text will take.

  • The model's own tokenizer. Built from the GGUF's vocabulary and merges, not an approximation from a separate library.
  • No weights, no deps. It reads the header and stops — pure standard library, nothing to install alongside the file.
  • BPE and SentencePiece. The two tokenizers GGUF files carry, covering the GPT-2 and Llama families; anything else is flagged, not guessed.
  • Packs to a budget. Trim a pile of text to the newest that fits a context window, and see exactly what was dropped.
Local AI tooling · benchmarking

tokrate

Benchmark a local OpenAI-compatible LLM endpoint — time-to-first-token, tokens/sec, and throughput under concurrency.

github.com/nirry-dev/tokrate

The first question about any local model is whether it's fast enough, and the answer is a few concrete numbers. tokrate gets them without a framework: it speaks the OpenAI streaming API that llama.cpp, vLLM and Ollama already serve, fires requests at a chosen concurrency, and times each one.

It reports the three numbers that matter on one machine — time to first token, per-request tokens/sec, and aggregate throughput — because a single stream's speed and what the box can do under load are different questions. Token counts come from the response's own usage, with a delta count as a fallback, and a warmup request loads the model so it isn't timed.

Point it at a URL, pick how many requests and how many at once, and read a table or JSON; a non-zero exit flags any failures. Pure standard library — it streams and times the responses itself. It sits beside oneslot and gguftok: one serves the model, one counts the prompt, this one measures the speed.

  • The numbers that matter. TTFT, tokens/sec per request, and aggregate throughput — latency and capacity, which aren't the same thing.
  • Speaks what you already run. Any OpenAI-compatible endpoint — llama.cpp, vLLM, Ollama, or oneslot — no adapter needed.
  • Concurrency on tap. Fire N requests C-at-a-time to see how throughput scales, with a warmup so load time isn't counted.
  • Standard library only. It streams and times the responses itself; a table or JSON, and a failure-aware exit code.
Recon & web Security · 05
Security tooling · vulnerability triage

vulntriage

Triage dependency CVEs by real-world exploitability, not by raw CVSS alone.

github.com/nirry-dev/vulntriage

vulntriage scans pinned dependencies and ranks their known CVEs by how likely they are to actually be exploited. It pulls findings from OSV, then enriches each with the CISA KEV catalog (is it exploited in the wild?) and FIRST EPSS (predicted exploit probability).

Those signals drive a P1–P4 rubric: anything in KEV is P1 regardless of score; high EPSS or CVSS lifts the rest. It computes CVSS v3.1 base scores itself from the vector string, de-duplicates records that alias the same CVE, and exits non-zero when anything urgent is found — so it works as a CI gate.

Pure standard library: urllib for the feeds, a thread pool for the per-package scan, and a small CLI that reads a requirements file or stdin and prints a ranked table or JSON.

  • Exploitability-ranked. OSV × CISA KEV × FIRST EPSS decide order — what to fix first, not just what scores highest.
  • Its own CVSS v3.1 math. Base scores computed from the vector per spec, scope-change handling included.
  • CI-ready. Non-zero exit on any P1/P2, table or JSON output, and an offline mode backed by a cached KEV.
  • Standard library only. urllib feeds, parallel scan, CVE de-duplication — and tested.
Security tooling · network recon

netprobe

Dependency-free TCP recon that maps a network into a graph of hosts and the services they expose.

github.com/nirry-dev/netprobe

netprobe sweeps hosts and ports with an async connect scan — a full TCP handshake, so it needs no raw sockets and no root — then fingerprints each open port: a banner grab, a single HTTP request, or a TLS handshake to read the certificate.

The idea that shapes it is that recon output is a graph, not a wall of text. Every finding becomes a host node, a service node carrying its attributes — banner, HTTP title and server, TLS certificate — and an edge between them, so a re-scan merges into the picture instead of replacing it. The graph exports to JSON or Graphviz, ready for the next tool.

Like the rest of the toolkit it's pure standard library: asyncio for the scan, bounded by a single semaphore; ssl for certificate inspection; and a small CLI that prints a table or emits the graph.

  • Recon as a graph. Hosts, services and their facts in one normalized model that merges across scans and feeds downstream tools.
  • Connect scan, no root. A portable full-handshake sweep over asyncio, bounded by one semaphore — thousands of ports without raw sockets.
  • Honest fingerprints. Banner, HTTP and TLS-certificate probes identify services; it records what it saw, with certs shown only when they validate.
  • Standard library only. asyncio scan, ssl cert reads, JSON / Graphviz export — no dependencies, with an offline end-to-end test suite.
Security tooling · authorized scope

inscope

An authorized-scope gate: declare an engagement's CIDRs, hosts and domains, then filter every target through it before it reaches a scanner.

github.com/nirry-dev/inscope

The rule that matters most on an engagement is the easiest to break: only touch what you're cleared to touch. inscope turns the scope — the CIDRs, hosts and domains you're authorized against, and the ones you must not hit — into a file you check every target through before it reaches a scanner.

It's built to be a pipe stage, so the safe thing is also the easy one — cat targets | inscope -s engagement.scope | nmap -iL - — and whatever comes out the other side is in scope by construction. Exclusions are checked first and win, IP targets match only IP rules and hosts only domain rules, and every refusal carries a reason you can log.

Matching is lexical and offline: it never resolves DNS, so running a check can't itself reach a host or leak a lookup. Pure standard library — ipaddress for the CIDR and range math, a small parser for the scope file, a Python API and a CLI. It's the Pentest Suite's "scope at the center" as one small gate you can drop into any toolkit.

  • Scope as a pipe stage. Filter targets down to the ones you're cleared for; the rest are dropped with a logged reason, not left to memory.
  • Exclusions win. Carve a gateway or a production host out of an in-scope range, and nothing later can put it back.
  • Offline by design. Lexical matching with no DNS, so a check never reaches a host — across IPs, CIDRs, ranges, domains and wildcards.
  • A verdict you can audit. Each decision names the target, the reason, and the rule that decided it. Standard library only.
Security tooling · TLS inspection

certpeek

Inspect the TLS certificate a host serves — expiry, SANs, issuer, chain trust and protocol — including certificates that don't validate.

github.com/nirry-dev/certpeek

The obvious way to read a certificate — ask the connection for it — only works when the certificate validates. So the moment one is expired, self-signed or wrong-hostname, the one time you most need to look, it comes back empty. certpeek works the way you'd want instead: it fetches the certificate with verification off, so any cert comes back, decodes it, and runs a separate validating connection just to learn whether the chain and hostname are trusted.

For each host it reports who the certificate is for and who signed it, its validity window and the days until it expires, the subject alternative names, and the TLS version negotiated — then flags what's wrong: expired, not yet valid, expiring soon, self-signed, untrusted, hostname mismatch, or a weak protocol.

It takes hosts, host:port or URLs from arguments or stdin, prints a line each or JSON, and sets a monitoring-friendly exit code — non-zero if anything's amiss — so certpeek api.example.com --days 14 || alert is a complete check. Pure standard library, with a Python API and a CLI.

  • Reads the broken ones. Expired, self-signed, wrong-hostname — the certificates you most need to see, which the usual API hides.
  • Facts and verdict. The certificate's details and whether the chain and hostname validate, from a separate trusted connection.
  • Built to monitor. A threshold for expiry and a non-zero exit when anything's wrong — one line in a cron job or a pipeline.
  • Standard library only. No dependencies; host / port / URL inputs, a table or JSON out.
Security tooling · web hardening

headerscope

Audit a site's HTTP security headers — HSTS, CSP, X-Frame-Options and the rest — with a letter grade and the fix for each gap.

github.com/nirry-dev/headerscope

Security headers are the cheapest hardening a site has and the easiest to forget. headerscope turns "are the headers set?" into one command: it fetches a URL and grades the response's security headers — HSTS, CSP, X-Content-Type-Options, X-Frame-Options, Referrer-Policy and the rest — with a letter grade and, for every gap, the one-line fix.

Each check is small and honest — a pass, a warning, or a failure with the reason — so the grade is something you can defend and watch over time. It reads the real response, error responses included, flags cookies missing Secure/HttpOnly/SameSite and headers that leak a server version, and never chases redirects down a rabbit hole.

A non-zero exit on any failure makes it a CI gate — a dropped CSP or an expired HSTS fails the build instead of going unnoticed. Pure standard library, with a Python API and a CLI. It rounds out the recon shelf beside netprobe and certpeek: find the service, check its certificate, grade its headers.

  • A grade, not a dump. Every header checked with pass / warn / fail and the exact fix — A through F, watchable over time.
  • Reads the real response. HSTS, CSP, frame options, cookies and info-leak headers — error responses audited too, no redirect chasing.
  • A CI gate. Non-zero exit on any failure, so a regression fails the build; URLs from args or stdin, a table or JSON.
  • Standard library only. One fetch per URL, no dependencies; --insecure for internal certs, nothing else to install.
Eval & guardrails AI safety · 05
LLM safety · guardrails

promptscreen

A guardrail for local LLM I/O: screen prompts and responses, redact secrets and PII, keep an audit trail.

github.com/nirry-dev/promptscreen

promptscreen runs one rule-and-detector pipeline over what goes into a model and what comes out of it. Prompt-injection and jailbreak heuristics guard the input; secret and PII detectors guard both directions. Every screen returns a decision — allow, flag or block — with the exact rule and span that caused it, never a silent change.

Flagged text is redacted to [category] placeholders and held in a keeper queue for review rather than dropped. Secrets are caught by named key formats and a Shannon-entropy fallback; card numbers are confirmed with a Luhn check. It's advisory by design — it routes doubt to a human, it doesn't pretend to be a classifier.

Its audit log is built so it can't become the leak: by default it records the decision, the hit locations, and a hash of the content — never the raw text or the matched secret. Pure standard library: regexes, a little entropy math, and hashlib.

  • A decision with its reasons. allow / flag / block, each hit naming the rule and the span — auditable and tunable, not a black box.
  • One pipeline, both directions. Injection on the way in, leaks and PII on the way out, from the same rules via a per-rule direction.
  • Redact and keep, don't drop. Flagged text is redacted and queued for review; secrets block, PII flags.
  • An audit log that can't leak. Hashes content and logs only hit locations by default — raw retention is an explicit opt-in.
LLM output · JSON repair

jsonmend

Turn almost-JSON from a model into valid JSON — tolerant repair, with optional JSON Schema validation and coercion.

github.com/nirry-dev/jsonmend

A model asked for JSON still wraps it in a sentence, fences it in Markdown, leaves a trailing comma, or gets cut off mid-object by a token limit. jsonmend sits between the model and json.loads and reads that generously — not by patching the text with regexes, but with a small, forgiving reader that rebuilds a real value and re-emits it as strict JSON.

In one pass it looks past prose and code fences, accepts single and curly quotes, unquoted keys, trailing and missing commas, // comments and Python's True/False/None, and reads lenient numbers. A stream cut off partway is simply closed — a reply that hit the token limit still parses as far as it got.

Hand it a JSON Schema and it holds the result to shape: a deliberate subset — types, enum, required, bounds — that can coerce the near-misses ("3" → 3, "yes" → true) and fill declared defaults. Pure standard library, with a CLI and an importable API.

  • A reader, not a regex. It parses almost-JSON the way a patient human would, so the next odd input doesn't break it.
  • Survives truncation. Anything still open at the end of a cut-off stream is closed, so a token-limited reply still parses.
  • Shapes to a schema. An honest JSON Schema subset validates, coerces the scalars that unambiguously fit, and fills defaults.
  • Never guesses. It won't invent fields you didn't get — a missing value is null or an error, not a fabrication.
SVG security · sanitizer

svgscrub

Sanitize untrusted or model-generated SVG into a safe drawing — strip scripts, handlers, foreign content and external references. Zero dependencies, Node and the browser.

github.com/nirry-dev/svgscrub

SVG is code. A file someone uploaded — or one a model just drew — can carry <script>, an onload handler, a <foreignObject> full of HTML, or a javascript: link, and the moment it's inlined in a page, that runs. svgscrub takes the SVG and gives back a drawing with all of it removed.

It works by allowlist, not blocklist: its own tokenizer keeps only known drawing elements and inert presentation attributes, and drops <script>, <style>, <foreignObject> and any unknown element with its whole subtree, every on* handler, and references that reach off the document — javascript: always, remote and data: URLs by default. What survives is re-serialized with escaping, so no value can break out of its attribute.

It brings its own parser and never touches the DOM, so the same code runs in Node and the browser with no dependencies. A reporting mode returns exactly what was taken out — a drawing that needed scrubbing is often worth noticing. Generalized from the sanitizer that keeps Lype's self-drawn SVG safe to show.

  • Allowlist, not blocklist. Unknown elements and attributes are dropped, so a new trick fails closed rather than open.
  • Kills the whole class. Scripts, event handlers, foreign content, and off-document URLs — removed, with the subtree they hid in.
  • No DOM, no deps. Its own tokenizer runs identically server-side and in the browser; nothing is fetched or executed while sanitizing.
  • Tells you what it took. A report of every removal, for logging or alerting — sanitizing silently hides the signal.
Integrity · tamper-evident log

sealedlog

A tamper-evident, append-only log: every entry is hash-chained to the one before it, so any change breaks the seals.

github.com/nirry-dev/sealedlog

An audit log is only worth as much as its resistance to editing — and a plain file can be rewritten by anyone who can write to it, silently. sealedlog makes edits evident: every entry is sealed with a hash over its own contents and the seal before it, so the records form a chain. Change any one — its content, its order, or anything earlier — and every seal from there on stops matching.

To check a log, it re-walks the chain and confirms every seal and link, pointing straight at the first broken record. The head seal is a fingerprint of the whole log: record it somewhere the writer can't reach, and you can prove nothing was altered up to that point. Sealing is SHA-256 so anyone can verify, or keyed HMAC when a secret should make the chain unforgeable.

It stores one JSON record per line, appends in a line of code, and verifies with a non-zero exit so it drops into a check. Pure standard library, with a Python API and a CLI. It's the sealed-record idea from Lype — which publishes its head after every night so its own history can't be quietly rewritten — as one small, readable library.

  • Edits don't hide. A hash chain over every record: change, reorder or drop one and the seals break, and verify names the first bad record.
  • Anchor the head. One seal fingerprints the whole log; publish it and a later rewrite can't reproduce it.
  • SHA-256 or keyed. Anyone can verify by default, or seal with a secret (HMAC) so the chain can't be forged at all.
  • Small and honest. Append-only JSONL, a pure chain core, no dependencies — and clear about what it can't catch (tail truncation) and why.
LLM eval · test harness

evalkit

A tiny offline eval harness: run a prompt suite against a local endpoint, score each output, and report pass-rates with variance.

github.com/nirry-dev/evalkit

You change a prompt, a model or a sampling setting, and you want to know if anything got worse — without standing up a whole eval framework. evalkit is the small version: a JSON file of cases, each a prompt and the checks its output must pass, run against the OpenAI-compatible endpoint your local server already speaks.

The checks are deterministic — substring, regex, exact match, valid JSON with required keys, length — so results reproduce. Run each case a few times and it makes flakiness a first-class result: a case that passes two times in three is a different thing from one that always passes, and evalkit says which is which.

It reports a per-case verdict — pass, flaky or fail — and an overall pass-rate, as a table or JSON, with a CI-friendly exit code keyed to a threshold you set. Pure standard library; the Python API takes any generate callable, so it drops into whatever harness you have. The guardrails sibling to promptscreen: one screens output at runtime, this one checks it before you ship.

  • Flakiness is a result. Repeat each case and see pass / flaky / fail, not just a lucky single run.
  • Deterministic checks. Substring, regex, exact match, valid JSON + required keys, length — reproducible, no model judging the answer.
  • A CI gate. A pass-rate threshold and a non-zero exit, so a prompt or model regression fails the build; a table or JSON.
  • Standard library only. A JSON suite, any OpenAI-compatible endpoint, and a Python API that takes any generate() — nothing to install.

All repositories on GitHub ↗

All repositories