[{"data":1,"prerenderedAt":230},["ShallowReactive",2],{"api:\u002Fapi\u002Fconfig:en":3,"api:\u002Fapi\u002Fprofile:":8,"api:\u002Fapi\u002Fcategories:en":20,"api:\u002Fapi\u002Fprojects\u002Fenvguard:en":164,"api:\u002Fapi\u002Fnotes:en":209},{"email":4,"lang":5,"name":6,"role":7},"hello@ewsirdev.example","en","Ewsirdev","Web developer: websites, landing pages and Go services",{"name":6,"handle":6,"contacts":9,"note":19},{"email":10,"github":13,"telegram":16},{"url":11,"label":4,"placeholder":12},"mailto:hello@ewsirdev.example",true,{"url":14,"label":15},"https:\u002F\u002Fgithub.com\u002Fewsir247","github.com\u002Fewsir247",{"url":17,"label":18},"https:\u002F\u002Ft.me\u002FEwsirdev","@Ewsirdev","email is a placeholder on a reserved domain: mail is not delivered. Telegram is the real channel. This file is the single source of contacts for the site, the contact block, JSON-LD sameAs, security.txt and llms.txt.",[21,50,78,100,136],{"slug":22,"title":23,"blurb":24,"variant":25,"palette":26,"layout":27,"count":28,"preview":29,"stack":45},"landings","Landing pages","One page, one job. Hand-written markup where possible, plus one WebGL experiment.","drop","cold","shots",6,[30,35,40],{"slug":31,"title":32,"shot":33,"hue":34},"landing-studio","Halftone: lighting studio","\u002Fshots\u002Flanding-studio-desktop-sm.webp",212,{"slug":36,"title":37,"shot":38,"hue":39},"landing-saas","Kill Switch: subscription tracker","\u002Fshots\u002Flanding-saas-desktop-sm.webp",182,{"slug":41,"title":42,"shot":43,"hue":44},"landing-event","Small Scale: conference","\u002Fshots\u002Flanding-event-desktop-sm.webp",124,[46,47,48,49],"CSS","HTML","GitHub Actions","JS",{"slug":51,"title":52,"blurb":53,"variant":54,"palette":55,"layout":56,"count":57,"preview":58,"stack":74},"sites","Multi-page sites","Sites with their own structure, routing and forms: from a client project to a set of niche pages.","lens","mono","frames",3,[59,64,69],{"slug":60,"title":61,"shot":62,"hue":63},"legal-multipage-site","Multi-page legal services website","\u002Fshots\u002Flegal-multipage-site-desktop-sm.webp",250,{"slug":65,"title":66,"shot":67,"hue":68},"niche-landings","Catalog of 10 niche demo landing pages","\u002Fshots\u002Fniche-landings-desktop-sm.webp",32,{"slug":70,"title":71,"shot":72,"hue":73},"docs-engine","Sverlo: a documentation generator in Node.js","\u002Fshots\u002Fdocs-engine-desktop-sm.webp",25,[48,75,76,77],"Vue 3","Bootstrap 5","Cloudflare Pages",{"slug":79,"title":80,"blurb":81,"variant":82,"palette":83,"layout":84,"count":85,"preview":86,"stack":97},"apps","Web apps","Interface plus server: auth, data, e-mail. Apps you sign in to.","merge","warm","phone",2,[87,92],{"slug":88,"title":89,"shot":90,"hue":91},"bad-club-booking-app","Bad Club: seat booking for a gaming coworking space","\u002Fshots\u002Fbad-club-booking-app-desktop-sm.webp",150,{"slug":93,"title":94,"shot":95,"hue":96},"typing-lab","typing-lab: a touch-typing trainer","\u002Fshots\u002Ftyping-lab-desktop-sm.webp",45,[75,77,98,99],"Express","GSAP",{"slug":101,"title":102,"blurb":103,"variant":104,"palette":105,"layout":106,"count":107,"langs":108,"preview":117,"stack":133},"backend","Backends","Services with an API, a database and tests: mostly Go, with some in Python and TypeScript. A language filter sits inside the section.","torus","acid","list",14,[109,112,115],{"lang":110,"count":111},"Go",12,{"lang":113,"count":114},"Python",1,{"lang":116,"count":114},"TypeScript",[118,123,128],{"slug":119,"title":120,"shot":121,"hue":122},"kanban-tasks","Amber: kanban board","\u002Fshots\u002Fkanban-tasks-desktop-sm.webp",344,{"slug":124,"title":125,"shot":126,"hue":127},"link-shortener","Short: link shortener","\u002Fshots\u002Flink-shortener-desktop-sm.webp",111,{"slug":129,"title":130,"shot":131,"hue":132},"booking-slots","Appointment booking","\u002Fshots\u002Fbooking-slots-desktop-sm.webp",65,[134,110,135,48],"Docker","SQLite",{"slug":137,"title":138,"blurb":139,"variant":140,"palette":55,"layout":141,"count":57,"langs":142,"preview":145,"stack":161},"cli","CLI tools","Terminal programs: one binary, clear flags, an exit code you can check in a script.","film","terminal",[143,144],{"lang":110,"count":85},{"lang":113,"count":114},[146,151,156],{"slug":147,"title":148,"shot":149,"hue":150},"gitpulse","GitPulse: git repository analytics","\u002Fshots\u002Fgitpulse-desktop-sm.webp",7,{"slug":152,"title":153,"shot":154,"hue":155},"logslice","logslice: log analysis with no dependencies","\u002Fshots\u002Flogslice-desktop-sm.webp",160,{"slug":157,"title":158,"shot":159,"hue":160},"envguard","envguard: a linter and a safe for .env files","\u002Fshots\u002Fenvguard-desktop-sm.webp",35,[162,134,48,163],"CLI","stdlib only",{"slug":157,"title":158,"type":137,"category":137,"lang":110,"stack":165,"description":171,"highlights":172,"run":177,"hue":160,"og":178,"shots":179,"case":186,"case_lang":5,"prev":208},[110,163,162,166,167,168,169,170,48,134],"AES-256-GCM","PBKDF2","fuzzing","govulncheck","gitleaks","A Go CLI with no external modules: check compares .env with a schema, scan looks for secrets in the tree and git history, diff and doctor check environments and git hygiene, seal and unseal encrypt .env into .env.sealed (AES-256-GCM, PBKDF2 with 600k iterations), run starts a command with a clean environment, report and serve build a report. A secret value never reaches the output, the log or the HTML. A side project.",[173,174,175,176],"Secret values are never printed: only the finding type, file, line and a 4-character mask","seal and unseal: AES-256-GCM, PBKDF2 with 600k iterations, any flipped bit of the file is rejected","Fuzz tests: 4 targets of 20 seconds each without crashes; govulncheck and gitleaks are clean","Builds for windows, linux and darwin, standard library only","go run .\u002Fcmd\u002Fenvguard check","\u002Fog\u002Fen\u002Fenvguard.jpg",{"desktop":180,"desktop_sm":159,"mobile":181,"dw":182,"dh":183,"mw":184,"mh":185},"\u002Fshots\u002Fenvguard-desktop.webp","\u002Fshots\u002Fenvguard-mobile.webp",1280,800,390,844,{"about":187,"features":190,"run":198,"notes":203},[188,189],"envguard is a Go console utility for .env files: it checks them against a schema, looks for secrets in the working tree and git history, encrypts .env into .env.sealed and runs commands with a clean environment. A side project.","One binary and only the Go standard library (there is no go.sum). The main rule of the project: a secret value never reaches the output, the log, an error, the HTML or the JSON. What is printed is the finding type, file, line and a four-character mask (for values of 12 characters and more); for short values only the length. Tests with canary values over the output of every command check this.",[191,192,193,194,195,196,197],"check: compares .env with .env.example and the envguard.schema schema (types int, bool, url, duration, enum, port, secret, string, ranges, duplicates, quotes, CRLF and BOM)","scan: secrets in known formats (AWS, GitHub, Cloudflare, Stripe, Slack, Google, PEM keys, JWT, a password in a URL) and two heuristics; exclusions through envguard:ignore and .envguardignore","diff compares environments in constant time and prints only the fact of a change and the length; doctor checks .gitignore, git tracking, file permissions and history","seal and unseal: AES-256-GCM, a key from a passphrase via PBKDF2 (600k iterations), the header as AAD; run starts a command with the decrypted environment without writing to disk, with a block list and output masking","report and serve: an HTML report, also on 127.0.0.1, without variable values, a strict CSP, Host and Origin checks","Tests: tables, golden files, canaries, seal property tests, 4 fuzz targets of 20 seconds each without crashes; govulncheck and gitleaks are clean","Cross-builds for windows, linux and darwin with a script, CI in GitHub Actions and a Dockerfile",[199,200,201,202],"git clone \u003Crepository on request> envguard","cd envguard","go build -o envguard .\u002Fcmd\u002Fenvguard",".\u002Fenvguard check   # .env against .env.example and the schema",[204,205,206,207],"There is no live version: it is a console utility.","In Go a secret cannot be reliably wiped from memory: passphrase and key buffers are zeroed, but variable values live in strings until garbage collection. Memory dumps and swap stay in the attacker model.","The scanner finds known formats and suspicious values; a secret of an arbitrary shape, encoded or inside a binary file, is missed, and the heuristics give false positives. Git history is checked in a limited way: the last N commits.","PBKDF2 is weaker against GPUs than scrypt and Argon2: that is the price of having no external modules. The -race check runs only in CI, file permissions are not checked on Windows.",{"slug":152,"title":153},[210,215,220,225],{"slug":211,"lang":5,"title":212,"date":213,"summary":214,"project":129,"minutes":114},"double-booking","Double booking: a transaction plus a partial UNIQUE index","2026-10-20","How booking-slots protects against two clients booking the same time at once, and how that protection is tested.",{"slug":216,"lang":5,"title":217,"date":213,"summary":218,"project":219,"minutes":85},"lease-and-fencing","Lease and fencing: how a queue neither loses a job nor lets a zombie finish it twice","A worker can crash, stall, or wake up after its job was taken by someone else. How the lease and the lease token in job-queue deal with that on SQLite.","job-queue",{"slug":221,"lang":5,"title":222,"date":213,"summary":223,"project":224,"minutes":85},"p95-nearest-rank","p95 in an uptime monitor: the nearest-rank method and what it does not hide","How uptime-monitor computes p50\u002Fp95 latency and uptime, why the nearest-rank method was chosen, and where those numbers can mislead.","uptime-monitor",{"slug":226,"lang":5,"title":227,"date":213,"summary":228,"project":229,"minutes":85},"websocket-from-scratch","WebSocket on the standard library: what really happens under new WebSocket()","Why realtime-rooms has its own RFC 6455 layer, which protocol violations it checks, and why a library is still the better choice for production.","realtime-rooms",1792819120091]