LLMs Full Content

# Orthogonal Info — Full Content Index

> Complete article content for AI systems. 122 articles indexed.
> Last updated: 2026-04-07

---
## ArgoCD vs Flux 2025: Secure CI/CD for Kubernetes

- URL: https://orthogonal.info/argocd-vs-flux-secure-gitops-kubernetes-2025/
- Date: 2026-04-06
- Category: Uncategorized
- Summary: I run ArgoCD on my TrueNAS homelab for all container deployments. Every service I self-host — Gitea, Immich, monitoring stacks, even this blog’s CI pipeline — gets deployed through ArgoCD syncing from Git repos on my local Gitea instance. I’ve also deployed Flux for clients who wanted something ligh

I run ArgoCD on my TrueNAS homelab for all container deployments. Every service I self-host — Gitea, Immich, monitoring stacks, even this blog’s CI pipeline — gets deployed through ArgoCD syncing from Git repos on my local Gitea instance. I’ve also deployed Flux for clients who wanted something lighter. After 12 years in Big Tech security engineering and thousands of hours operating both tools, here’s my honest comparison — not the sanitized vendor version, but what actually matters when you’re on-call at 2 AM and a deployment is stuck. Why This Comparison Still Matters in 2025 📋 TL;DR This article compares ArgoCD vs Flux 2025 with practical guidance for production environments. 🎯 Quick Answer: ArgoCD is the better choice for most teams in 2025—it offers a built-in web UI, RBAC, and multi-cluster support out of the box. Flux is lighter and more composable but requires assembling your own dashboard and access controls. “GitOps is just version control for Kubernetes.” If you’ve heard this, you’ve been sold a myth. GitOps is much more than syncing manifests to clusters — it’s a fundamentally different approach to how we manage infrastructure and applications. And in 2025, with Kubernetes still dominating container orchestration, ArgoCD and Flux remain the two main contenders. Supply chain attacks are up 742% since 2020 according to Sonatype’s latest report. SLSA compliance requirements are real. The executive order on software supply chain security means your GitOps tool isn’t just a convenience — it’s part of your compliance story. Choosing between ArgoCD and Flux isn’t just a features checklist; it’s a security architecture decision that affects your audit posture. My ArgoCD Setup: Real Configuration from My Homelab Let me show you exactly what I run. My TrueNAS server hosts a k3s cluster with ArgoCD managing everything. Here’s the actual Application manifest I use to deploy my Gitea instance — not a sanitized tutorial version, but real config with the patterns I’ve settled on after months of iteration: apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: gitea namespace: argocd labels: app.kubernetes.io/part-of: homelab environment: production finalizers: - resources-finalizer.argocd.argoproj.io spec: project: homelab-apps source: repoURL: https://gitea.192.168.0.62.nip.io/deployer/homelab-manifests.git targetRevision: main path: apps/gitea helm: releaseName: gitea valueFiles: - values.yaml - values-production.yaml parameters: - name: gitea.config.server.ROOT_URL value: "https://gitea.192.168.0.62.nip.io" - name: persistence.size value: "50Gi" - name: persistence.storageClass value: "truenas-iscsi" destination: server: https://kubernetes.default.svc namespace: gitea syncPolicy: automated: prune: true selfHeal: true allowEmpty: false syncOptions: - CreateNamespace=true - PrunePropagationPolicy=foreground - PruneLast=true - ServerSideApply=true retry: limit: 3 backoff: duration: 5s factor: 2 maxDuration: 3m A few things to note about this config. The resources-finalizer ensures ArgoCD cleans up resources when you delete the Application — without it, you get orphaned pods and services cluttering your cluster. The selfHeal: true flag is critical: if someone manually kubectl edits a resource, ArgoCD reverts it to match Git. This is the real power of GitOps — Git is the single source of truth, not whatever someone typed at 3 AM during an incident. The ServerSideApply sync option is something I added after hitting CRD conflicts. Kubernetes server-side apply handles field ownership correctly, which matters when you have multiple controllers touching the same resources. If you’re running cert-manager, external-dns, or any other controller that modifies resources ArgoCD manages, enable this. Flux HelmRelease: The Equivalent Setup For comparison, here’s how the same Gitea deployment looks in Flux. I set this up for a client who wanted a lighter footprint — their single-cluster setup didn’t need ArgoCD’s overhead: --- apiVersion: source.toolkit.fluxcd.io/v1 kind: GitRepository metadata: name: homelab-manifests namespace: flux-system spec: interval: 5m url: https://gitea.192.168.0.62.nip.io/deployer/homelab-manifests.git ref: branch: main secretRef: name: gitea-credentials --- apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: gitea namespace: gitea spec: interval: 30m chart: spec: chart: ./apps/gitea sourceRef: kind: GitRepository name: homelab-manifests namespace: flux-system values: gitea: config: server: ROOT_URL: "https://gitea.192.168.0.62.nip.io" persistence: size: 50Gi storageClass: truenas-iscsi install: createNamespace: true remediation: retries: 3 upgrade: remediation: retries: 3 remediateLastFailure: true cleanupOnFail: true rollback: timeout: 5m cleanupOnFail: true Notice the difference immediately: Flux splits the concern into two resources — a GitRepository source and a HelmRelease that references it. ArgoCD bundles everything into one Application manifest. Flux’s approach is more composable (you can reuse the same GitRepository across multiple HelmReleases), but ArgoCD’s single-resource model is easier to reason about when you’re scanning through a directory of manifests. The remediation blocks in Flux are the equivalent of ArgoCD’s retry policy. Flux’s rollback configuration is more explicit — you define exactly what happens on failure at each lifecycle stage (install, upgrade, rollback). ArgoCD handles this more automatically with selfHeal, which is simpler but gives you less granular control. Side-by-Side Feature Comparison After running both tools extensively, here’s my honest feature-by-feature breakdown. This isn’t marketing copy — it’s what I’ve observed in production: Feature ArgoCD Flux My Verdict Web UI Built-in dashboard with real-time sync status, diff views, and log streaming No native UI. Weave GitOps dashboard available as add-on ArgoCD wins decisively Multi-cluster Single instance manages all clusters via ApplicationSet Deploy controllers per-cluster, manage via Git ArgoCD for centralized; Flux for resilience Helm Support Native Helm rendering, parameters in Application spec HelmRelease CRD with full lifecycle management Flux has better Helm lifecycle hooks Kustomize Native support, automatic detection Native support via Kustomization CRD Tie — both excellent RBAC Built-in RBAC with projects, roles, and SSO integration Kubernetes-native RBAC only ArgoCD for enterprise, Flux for simplicity Secrets Native Vault, AWS SM, GCP SM integrations SOPS, Sealed Secrets, external-secrets-operator ArgoCD easier out of box; Flux more flexible Notifications argocd-notifications with Slack, Teams, webhook, email Flux notification-controller with similar integrations Tie — both work well Image Automation Requires Argo Image Updater (separate project) Built-in image-reflector and image-automation controllers Flux wins — native and mature Resource Footprint ~500MB RAM for server + repo-server + controller ~200MB RAM across all controllers Flux is significantly lighter Learning Curve Lower — UI helps, single resource model Steeper — multiple CRDs, CLI-first workflow ArgoCD for onboarding new teams Drift Detection Real-time with visual diff in UI Periodic reconciliation (configurable interval) ArgoCD for immediate visibility OCI Registry Support Supported since v2.8 Native support for OCI artifacts as sources Flux pioneered this; both solid now Core Architecture: How They Differ Deployment Models ArgoCD runs as a standalone application inside your cluster. It watches Git repos and applies changes continuously. The declarative model makes debugging straightforward — you can see exactly what state ArgoCD thinks the cluster should be in versus what’s actually running. Flux takes a different approach. It’s a set of Kubernetes controllers that use native CRDs to manage deployments. Lighter footprint, tighter coupling with the cluster API. Less magic, more Kubernetes-native. If you’re the kind of engineer who thinks in te


---
## OAuth vs JWT: Choosing the Right Tool for Developers

- URL: https://orthogonal.info/oauth-vs-jwt-choosing-the-right-tool/
- Date: 2026-04-05
- Category: Security
- Summary: I’ve implemented both OAuth and JWT in production systems across my career—from enterprise SSO rollouts to lightweight API auth for side projects. The single most common mistake I see? Treating OAuth and JWT as the same thing, or worse, picking one when you needed the other. They solve different pro

I’ve implemented both OAuth and JWT in production systems across my career—from enterprise SSO rollouts to lightweight API auth for side projects. The single most common mistake I see? Treating OAuth and JWT as the same thing, or worse, picking one when you needed the other. They solve different problems, and confusing them leads to real vulnerabilities. Here’s what each actually does, when to pick which, and how to avoid the traps I’ve seen burn teams in production. OAuth and JWT Are Not the Same Thing 🔧 From my experience: The worst auth bugs I’ve triaged all came from teams using JWT as a session token without a revocation strategy. When a compromised token has a 24-hour expiry and no blacklist, you’re stuck watching an attacker operate for hours. Always pair JWTs with a server-side revocation check for anything security-critical. 📌 TL;DR: OAuth and JWT are distinct tools serving different purposes: OAuth is a protocol for delegated authorization, while JWT is a compact, signed data format for carrying claims. OAuth is ideal for third-party integrations requiring secure delegation, whereas JWT excels in lightweight, stateless authentication within microservices. 🎯 Quick Answer: OAuth is an authorization protocol that delegates access without sharing passwords; JWT is a signed token format for carrying identity claims. Use OAuth for third-party login flows and JWT for stateless API authentication—they solve different problems and are often used together. OAuth is a protocol for delegated authorization. It defines how tokens get issued, exchanged, and revoked when one service needs to act on behalf of a user. Think “Log in with Google” — the user never gives their Google password to your app. OAuth handles the handshake. JWT (JSON Web Token) is a data format. It’s a signed, self-contained blob of JSON that carries claims — who the user is, what they can do, when the token expires. OAuth can use JWT as its token format, but JWT exists independently of OAuth. The valet key analogy works: OAuth is the process of getting the valet key from the car owner. JWT is the key itself — compact, verifiable, and self-contained. Here’s what a JWT payload looks like: { "sub": "1234567890", "name": "John Doe", "admin": true, "iat": 1516239022 } The sub field identifies the user, admin is a permission claim, and iat is the issue timestamp. The whole thing is signed — tamper with any field and validation fails. The Real Differences That Matter Here’s where the confusion gets dangerous: Validation model: OAuth tokens are typically validated by calling the authorization server (network round-trip). JWTs are validated locally using the token’s cryptographic signature. This makes JWT validation faster — critical in microservices where every millisecond counts. Statefulness: OAuth maintains state on the authorization server (it knows which tokens are active). JWT is stateless — the server doesn’t store anything. This is a strength and a weakness (more on revocation below). Scope: OAuth defines the entire authorization flow — redirect URIs, scopes, grant types. JWT just structures and signs data. You can use JWT for things that have nothing to do with OAuth. In practice, many systems use both: OAuth for the authorization flow, JWT as the token format. But you can also use JWT standalone for stateless session management between your own services. const jwt = require('jsonwebtoken'); const publicKey = process.env.PUBLIC_KEY; try { const decoded = jwt.verify(token, publicKey); console.log('Token is valid:', decoded); } catch (err) { console.error('Invalid token:', err.message); } When to Use Which Pick OAuth when: Third parties are involved. If users need to grant access to external services — social logins, API integrations, “Connect your Slack account” — OAuth provides the framework for safe delegation. You’re not sharing passwords; you’re issuing scoped, revocable tokens. Pick JWT when: You need lightweight, stateless authentication between your own services. In a microservices setup, passing a signed JWT between services beats hitting a central auth server on every request. It’s faster and removes a single point of failure. Use both when: You want OAuth for the auth flow but JWT for the actual token. This is the most common production pattern I see — OAuth issues a JWT, and downstream services validate it locally without talking to the auth server. For example: an e-commerce platform where OAuth authenticates users at the gateway, then JWTs carry user claims to the cart, inventory, and payment services. Each service validates the JWT signature independently. No shared session store needed. Security Practices That Actually Matter I’ve seen every one of these mistakes in production code: Don’t store tokens in localStorage. It’s wide open to XSS attacks. Use secure, HTTP-only cookies. If a script can read it, an attacker’s script can too. Set short expiration times. A JWT that lives forever is a JWT waiting to be stolen. I default to 15 minutes for access tokens, paired with refresh tokens for extended sessions. Rotate signing keys. If your signing key is compromised and you’ve been using the same one for two years, every token you’ve ever issued is compromised. Rotate regularly and publish your public keys via a JWKS endpoint. Don’t put sensitive data in JWT claims. JWTs are signed, not encrypted (by default). Anyone can decode the payload — they just can’t modify it. Never put passwords, credit card numbers, or PII in claims. ⚠️ Security Note: Avoid hardcoding secrets in your codebase. Use environment variables or a proper secrets management system. Use established libraries. passport.js for OAuth, jsonwebtoken for JWT. These have been battle-tested by thousands of projects. Rolling your own auth is how security vulnerabilities happen. The JWT Revocation Problem (and How to Solve It) This is the one thing that trips people up. JWTs are stateless — once issued, the server has no way to “take them back.” If a user logs out or their account is compromised, that JWT is still valid until it expires. Two approaches that work: Token blacklist: Maintain a list of revoked token IDs (jti claim) in Redis or similar. Check against it during validation. Yes, this adds state — but only for revoked tokens, not all active ones. Short-lived tokens + refresh tokens: Keep access tokens short (5-15 min). Use long-lived refresh tokens (stored in HTTP-only cookies) to get new access tokens. When you need to revoke, kill the refresh token. The access token dies naturally within minutes. I prefer the second approach. It keeps the system mostly stateless while giving you a revocation mechanism that works in practice. The refresh token lives server-side (or in a secure cookie), and revoking it is as simple as deleting it from your store. 💡 Quick rule of thumb: If you’re building a single app with one backend, you probably just need JWT. If third parties need access to your users’ data, you need OAuth. If you’re running microservices, you likely want both. 🛠️ Recommended Resources:Tools and books I’ve actually used or referenced while working on auth systems:Zero Trust Networks — Building secure systems in untrusted networks ($40-50)YubiKey 5 NFC — Hardware security key for SSH, GPG, and MFA ($45-55)The Web Application Hacker’s Handbook — Finding and exploiting security flaws in web apps ($35-45)Threat Modeling: Designing for Security — Systematic approach to finding and addressing threats ($35-45) Get daily AI-powered market intelligence. Join Alpha Signal — free market briefs, security alerts, and dev tool recommendations. Frequently Asked Questions What is the main difference between OAuth and JWT? OAuth is a protocol for delegated authorization, managing token issuance and revocation, while JWT is a signed, self-contained data format used to carry claims and validate them locally. Can OAuth use JWT as its token format? Yes, OAuth can use JWT as its token format, but JWT also exists independentl


---
## PassForge: Building a Password Workstation Beyond One Slider

- URL: https://orthogonal.info/passforge-password-generator-workstation/
- Date: 2026-04-04
- Category: Security
- Summary: Generate passwords, passphrases, test strength, and bulk-generate — all in one privacy-first browser tool with zero dependencies.

I was setting up a new server last week and needed twelve unique passwords for different services. I opened three tabs — LastPass’s generator, Bitwarden’s generator, and 1Password’s online tool. Every single one gave me a barebones interface: one slider for length, a few checkboxes, and a single output. Copy, switch tabs, paste, repeat. Twelve times. That’s when I decided to build PassForge — a password workstation that handles everything in one place: random passwords, memorable passphrases, strength testing, and bulk generation. All running in your browser with zero data leaving your machine. What makes PassForge different 📌 TL;DR: PassForge is a browser-based password workstation that offers advanced features like random password generation, memorable passphrase creation, strength testing, and bulk generation. It prioritizes cryptographic randomness, operates entirely offline, and is built as a lightweight, single HTML file with no external dependencies. 🎯 Quick Answer: PassForge is a free, browser-based password workstation that combines random password generation, passphrase creation, strength testing, and bulk generation in one tool—all processing happens locally with zero server uploads. Most password generators solve one narrow problem: they spit out a random string. PassForge treats passwords as a workflow with four distinct modes. Password Generator handles the classic use case — random character strings with fine-grained control. You pick a length from 4 to 128 characters, toggle character sets (uppercase, lowercase, digits, symbols), and optionally exclude ambiguous characters like O/0 and l/1/I. Every generated password pulls from crypto.getRandomValues(), not Math.random(), so you get real cryptographic randomness. Passphrase Generator is where things get interesting. Instead of random characters, it builds multi-word phrases from a curated 1,296-word dictionary (based on the EFF short wordlist). A 5-word passphrase like “Bold-Crane-Melt-Surf-Knot” carries about 52 bits of entropy — comparable to a random 10-character password — but you can actually remember it. You can pick separator style (dash, dot, underscore, space), capitalize words, and optionally append a number or symbol for sites with strict requirements. Strength Tester lets you paste any existing password and get an honest assessment. It calculates entropy, estimates crack time assuming a 10-billion-guesses-per-second GPU cluster, and runs pattern analysis for repeated characters, sequential sequences, and character diversity. The visibility toggle lets you inspect the password without exposing it to shoulder surfers by default. Bulk Generator solves my original problem — generating many passwords at once. Slider from 2 to 50, choice between random passwords and passphrases, click any row to copy it, or hit “Copy All” to get the entire batch on your clipboard separated by newlines. How it actually works under the hood The entire app is a single HTML file — 40KB total, zero external dependencies. No frameworks, no CDN requests, no analytics pixels. When you open it, you get first paint in under 100ms because there’s nothing to fetch. Cryptographic randomness Every random value in PassForge comes from the Web Crypto API. The cryptoRandInt(max) function creates a Uint32Array, fills it with crypto-grade random bytes, and takes the modulus. For shuffling (ensuring character set distribution), I use the Fisher-Yates algorithm with crypto random indices. function cryptoRandInt(max) { const arr = new Uint32Array(1); crypto.getRandomValues(arr); return arr[0] % max; } The password generator guarantees at least one character from each active set, then fills the remaining length from the combined pool, then shuffles the entire result. This prevents the “first 4 chars are always one-from-each-set” pattern that weaker generators produce. Entropy calculation Entropy is calculated as length × log₂(poolSize), where pool size is determined by which character classes appear in the password. For passphrases, it’s wordCount × log₂(dictionarySize) — with our 1,296-word list, each word adds about 10.34 bits. The crack time estimate assumes a high-end adversary: 10 billion guesses per second, which is what a multi-GPU rig running Hashcat can achieve against fast hashes like MD5. Against bcrypt or Argon2, actual crack times would be orders of magnitude longer. I chose the aggressive estimate because your password should be strong even against the worst-case scenario. The strength tester’s pattern analysis Beyond raw entropy, the tester checks for real weaknesses: Repeated characters — catches “aaa” or “111” runs (regex: /(.){2,}/) Sequential characters — detects keyboard walks like “abc”, “123”, or “qwerty” substrings Character diversity — unique characters as a percentage of total length; below 50% is a red flag Missing character classes — flags when uppercase, lowercase, digits, or symbols are absent Each check produces a clear pass/fail with a specific tip for improvement, not just a vague “make it stronger” message. Design decisions I’m opinionated about Dark mode is automatic. PassForge reads prefers-color-scheme and switches themes without any toggle button. If your OS says dark, you get dark. No cookie banners, no preference dialogs. Every output is one-click copy. Click the password box, click a bulk list row, click the passphrase — they all copy to clipboard with a 2-second toast confirmation. No separate copy button hunting. Touch targets are 44px minimum. Every interactive element — tabs, checkboxes, sliders, buttons — meets Apple’s Human Interface Guidelines for minimum touch target size. This matters when you’re generating a password on your phone in a coffee shop. Keyboard navigation works throughout. Tabs use arrow keys. Checkboxes respond to Space and Enter. Ctrl+G generates a new password regardless of which tab you’re on. Focus states are visible. PWA-installable. PassForge includes a service worker and web manifest, so you can “Add to Home Screen” on mobile or install it as a desktop app. It works offline after the first load — your password generator should never depend on an internet connection. When you’d actually use each mode Password mode — database credentials, API keys, service accounts, anything a machine reads. Max length, all character sets, exclude ambiguous. Passphrase mode — your primary email, password manager master password, full-disk encryption. Anything you type by hand and need to remember. Strength tester — auditing existing passwords. Paste your current bank password and find out if it’s actually as strong as you assumed. Bulk mode — provisioning new infrastructure, creating test accounts, rotating credentials across services. Privacy is structural, not promised PassForge doesn’t have analytics. It doesn’t make network requests after loading. There’s no server-side component to hack, no database to breach, no logs to subpoena. Open your browser’s network tab while using it — you’ll see exactly zero requests. Your passwords exist in your browser’s memory and nowhere else. This isn’t a privacy policy I wrote to sound good. It’s a consequence of the architecture: single HTML file, no backend, no external scripts. Try it PassForge is free and ready to use right now. If you find it useful, I’d appreciate a . If you work with passwords daily — sysadmin, developer, IT support — bookmark it. It’s built to be the one password tool you keep open. Related tools and reads: HashForge — generate and verify MD5/SHA/HMAC hashes, all in your browser DiffLab — compare text diffs without uploading anything RegexLab — test regex patterns with a multi-case runner YubiKey SSH Authentication — pair PassForge with hardware security keys for real protection Browser Fingerprinting — why strong passwords alone aren’t enough for online privacy Equip your setup with reliable gear: a YubiKey 5C NFC for hardware-backed 2FA, a mechanical keyboard for comfortable password entry, and a pr


---
## YubiKey SSH Authentication: Stop Trusting Key Files on Disk

- URL: https://orthogonal.info/yubikey-ssh-authentication-stop-trusting-key-files-on-disk/
- Date: 2026-04-03
- Category: Security
- Summary: How to set up YubiKey FIDO2 resident keys for SSH authentication. Your private key stays on hardware — it can’t be copied, dumped, or stolen remotely. Full walkthrough with gotchas.

I stopped using SSH passwords three years ago. Switched to ed25519 keys, felt pretty good about it. Then my laptop got stolen from a coffee shop — lid open, session unlocked. My private key was sitting right there in ~/.ssh/, passphrase cached in the agent. That’s when I bought my first YubiKey. Why a Hardware Key Beats a Private Key File 📌 TL;DR: YubiKey provides secure SSH authentication by storing private keys on hardware, preventing extraction or misuse even if a device is stolen or compromised. Unlike disk-stored keys, YubiKey requires physical touch for authentication, adding an extra layer of security. It supports FIDO2/resident keys and works across devices with USB-C or NFC options. 🎯 Quick Answer: YubiKey SSH authentication stores your private key on tamper-resistant hardware so it cannot be copied or extracted, even if your machine is compromised. Configure it via ssh-keygen -t ed25519-sk to bind SSH keys to the physical device. Your SSH private key lives on disk. Even if it’s passphrase-protected, once the agent unlocks it, it’s in memory. Malware can dump it. A stolen laptop might still have an active agent session. Your key file can be copied without you knowing. A YubiKey stores the private key on the hardware. It never leaves the device. Every authentication requires a physical touch. No touch, no auth. Someone steals your laptop? They still need the physical key plugged in and your finger on it. That’s the difference between “my key is encrypted” and “my key literally cannot be extracted.” Which YubiKey to Get For SSH, you want a YubiKey that supports FIDO2/resident keys. Here’s what I’d recommend: YubiKey 5C NFC — my top pick. USB-C fits modern laptops, and the NFC means you can tap it on your phone for GitHub/Google auth too. Around $55, and I genuinely think it’s the best value if you work across multiple devices. (Full disclosure: affiliate link) If you’re on a tighter budget, the YubiKey 5 NFC (USB-A) does the same thing for about $50, just with the older port. Still a good option if your machines have USB-A. One important note: buy two. Register both with every service. Keep one on your keychain, one locked in a drawer. If you lose your primary, you’re not locked out of everything. I learned this the hard way with a 2FA lockout that took three days to resolve. Setting Up SSH with FIDO2 Resident Keys You need OpenSSH 8.2+ (check with ssh -V). Most modern distros ship with this. If you’re on macOS, the built-in OpenSSH works fine since Ventura. First, generate a resident key stored directly on the YubiKey: ssh-keygen -t ed25519-sk -O resident -O verify-required -C "yubikey-primary" Breaking this down: -t ed25519-sk — uses the ed25519 algorithm backed by a security key (sk = security key) -O resident — stores the key on the YubiKey, not just a reference to it -O verify-required — requires PIN + touch every time (not just touch) -C "yubikey-primary" — label it so you know which key this is It’ll ask you to set a PIN if you haven’t already. Pick something decent — this is your second factor alongside the physical touch. You’ll end up with two files: id_ed25519_sk and id_ed25519_sk.pub. The private file is actually just a handle — the real private key material lives on the YubiKey. Even if someone gets this file, it’s useless without the physical hardware. Adding the Key to Remote Servers Same as any SSH key: ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub user@your-server Or manually append the public key to ~/.ssh/authorized_keys on the target machine. When you SSH in, you’ll see: Confirm user presence for key ED25519-SK SHA256:... User presence confirmed That “confirm user presence” line means it’s waiting for you to physically tap the YubiKey. No tap within ~15 seconds? Connection refused. I love this — it’s impossible to accidentally leave a session auto-connecting in the background. The Resident Key Trick: Any Machine, No Key Files This is the feature that sold me. Because the key is resident (stored on the YubiKey itself), you can pull it onto any machine: ssh-keygen -K That’s it. Plug in your YubiKey, run that command, and it downloads the key handles to your current machine. Now you can SSH from a fresh laptop, a coworker’s machine, or a server — as long as you have the YubiKey plugged in. No more syncing ~/.ssh folders across machines. No more “I need to get my key from my other laptop.” The YubiKey is the key. Hardening sshd for Key-Only Auth Once your YubiKey is working, lock down the server. In /etc/ssh/sshd_config: PasswordAuthentication no KbdInteractiveAuthentication no PubkeyAuthentication yes AuthenticationMethods publickey Reload sshd (systemctl reload sshd) and test with a new terminal before closing your current session. I’ve locked myself out exactly once by reloading before testing. Don’t be me. If you want to go further, you can restrict to only FIDO2 keys by requiring the sk key types in your authorized_keys entries. But for most setups, just disabling passwords is the big win. What About Git and GitHub? GitHub has supported security keys for SSH since late 2021. Add your id_ed25519_sk.pub in Settings → SSH Keys, same as any other key. Every git push and git pull now requires a physical touch. It adds maybe half a second to each operation. I was worried this would be annoying — it’s actually reassuring. Every push is a conscious decision. For your Git config, make sure you’re using the SSH URL format: git remote set-url origin [email protected]:username/repo.git Gotchas I Hit Agent forwarding doesn’t work with FIDO2 keys. The touch requirement is local — you can’t forward it through an SSH jump host. If you rely on agent forwarding, you’ll need to either set up ProxyJump or keep a regular ed25519 key for jump scenarios. macOS Sonoma has a quirk where the built-in SSH agent sometimes doesn’t prompt for the touch correctly. Fix: add SecurityKeyProvider internal to your ~/.ssh/config. WSL2 can’t see USB devices by default. You’ll need usbipd-win to pass the YubiKey through. It works fine once set up, but the initial config is a 10-minute detour. VMs need USB passthrough configured. In VirtualBox, add a USB filter for “Yubico YubiKey.” In QEMU/libvirt, use hostdev passthrough. This catches people off guard when they SSH from inside a VM and wonder why the key isn’t detected. My Setup I carry a YubiKey 5C NFC on my keychain and keep a backup YubiKey 5 Nano in my desk. The Nano stays semi-permanently in my desktop’s USB port — it’s tiny enough that it doesn’t stick out. (Full disclosure: affiliate links) Both keys are registered on every server, GitHub, and every service that supports FIDO2. If I lose my keychain, I walk to my desk and keep working. Total cost: about $80 for two keys. For context, that’s less than a month of most password manager premium plans, and it protects against a class of attacks that passwords simply can’t. Should You Bother? If you SSH into anything regularly — servers, homelabs, CI runners — yes. The setup takes 15 minutes, and the daily friction is a light tap on a USB device. The protection you get (key material that physically can’t be stolen remotely) is worth way more than the cost. If you’re already running a homelab with TrueNAS or managing Docker containers, this is a natural next step in locking things down. Hardware keys fill the gap between “I use SSH keys” and “my infrastructure is actually secure.” Start with one key, test it for a week, then buy the backup. You won’t go back. Join Alpha Signal for free market intelligence — daily briefings on tech, AI, and the markets that drive them. 📚 Related Reading Secrets Management in Kubernetes: A Security-First Guide Zero Trust for Developers: Simplifying Security References Yubico — “Using Your YubiKey with SSH” OWASP — “Authentication Cheat Sheet” GitHub — “YubiKey-SSH Configuration Guide” NIST — “Digital Identity Guidelines” RFC Editor — “RFC 4253: The Secure Shell (SSH) Transport Layer Protocol” Frequently Asked Questions Why is YubiKey m


---
## Browser Fingerprinting: Identify You Without Cookies

- URL: https://orthogonal.info/browser-fingerprinting-how-12-lines-of-javascript-identify-you-without-cookies/
- Date: 2026-04-03
- Category: Security
- Summary: How Canvas API, AudioContext, and WebGL fingerprint your browser without cookies. Code examples, entropy measurements, and what actually defends against it.

Last month I was debugging a tracking issue for a client and realized something uncomfortable: even after clearing all cookies and using a fresh incognito window, a third-party analytics script was still identifying the same user session. No cookies, no localStorage, no URL parameters. Just JavaScript reading properties that every browser willingly exposes. Browser fingerprinting isn’t new, but most developers I talk to still underestimate how effective it is. The EFF’s Cover Your Tracks project found that 83.6% of browsers have a unique fingerprint. Not “somewhat unique” — unique. One in a million. And that number climbs to over 94% if you include Flash or Java metadata (though those are mostly dead now). I spent a weekend building a fingerprinting test page to understand exactly what data points create this uniqueness. Here’s what I found. The Canvas API: Your GPU’s Signature 📌 TL;DR: Browser fingerprinting allows websites to uniquely identify users without cookies by leveraging browser-exposed properties like Canvas and Audio APIs. These techniques exploit hardware and software variations to generate unique identifiers, making privacy protection more challenging. Studies show that over 83% of browsers have unique fingerprints, highlighting its effectiveness. 🎯 Quick Answer: Browser fingerprinting uniquely identifies users without cookies by combining Canvas rendering, AudioContext output, WebGL parameters, and installed fonts into a hash. Studies show this technique can uniquely identify over 90% of browsers. This is the technique that surprised me most. The HTML5 Canvas API renders text and shapes slightly differently depending on your GPU, driver version, OS font rendering engine, and anti-aliasing settings. The differences are invisible to the eye but consistent across page loads. Here’s a minimal Canvas fingerprint in 12 lines: function getCanvasFingerprint() { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); ctx.textBaseline = 'top'; ctx.font = '14px Arial'; ctx.fillStyle = '#f60'; ctx.fillRect(125, 1, 62, 20); ctx.fillStyle = '#069'; ctx.fillText('Browser fingerprint', 2, 15); ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'; ctx.fillText('Browser fingerprint', 4, 17); return canvas.toDataURL(); } That toDataURL() call returns a base64 string representing the rendered pixels. On my M2 MacBook running Chrome 124, this produces a hash that differs from the same code on Chrome 124 on my Intel Linux box. Same browser version, same text, different pixel output. Why? The font rasterizer (FreeType vs Core Text vs DirectWrite), sub-pixel rendering settings, and GPU shader behavior all introduce tiny variations. A 2016 Princeton study tested this across 1 million browsers and found Canvas fingerprinting alone could identify about 50% of users — no cookies needed. AudioContext: Your Sound Card Leaks Too This one is less well-known. The Web Audio API processes audio signals with slight floating-point variations depending on the hardware and driver stack. You don’t even need to play a sound: async function getAudioFingerprint() { const ctx = new (window.OfflineAudioContext || window.webkitOfflineAudioContext)(1, 44100, 44100); const oscillator = ctx.createOscillator(); oscillator.type = 'triangle'; oscillator.frequency.setValueAtTime(10000, ctx.currentTime); const compressor = ctx.createDynamicsCompressor(); compressor.threshold.setValueAtTime(-50, ctx.currentTime); compressor.knee.setValueAtTime(40, ctx.currentTime); compressor.ratio.setValueAtTime(12, ctx.currentTime); oscillator.connect(compressor); compressor.connect(ctx.destination); oscillator.start(0); const buffer = await ctx.startRendering(); const data = buffer.getChannelData(0); // Hash the first 4500 samples return data.slice(0, 4500).reduce((a, b) => a + Math.abs(b), 0); } The resulting float sum varies by sound card and audio driver. Combined with Canvas, you’re looking at roughly 70-80% unique identification rates. I tested this on three machines in my homelab — all running Debian 12, all with different audio chipsets — and got three distinct values. WebGL: GPU Model Exposed WebGL goes further than Canvas. It exposes your GPU vendor, renderer string, and supported extensions. Most browsers serve this up without hesitation: function getWebGLInfo() { const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl'); const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); return { vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL), renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL), extensions: gl.getSupportedExtensions().length }; } On my machine this returns Apple / Apple M2 / 47 extensions. That renderer string alone narrows the pool considerably. Combine it with the exact list of supported extensions (which varies by driver version) and you’ve got another high-entropy signal. The Full Fingerprint Stack A real fingerprinting library like FingerprintJS (now Fingerprint.com) combines 30+ signals. Here are the ones that contribute the most entropy, based on my testing: Signal Entropy (bits) How It Works User-Agent string ~10 OS + browser + version Canvas hash ~8-10 GPU + font rendering WebGL renderer ~7 GPU model + driver Installed fonts ~6-8 Font enumeration via CSS Screen resolution + DPR ~5 Monitor + scaling AudioContext ~5-6 Audio processing differences Timezone + locale ~4 Intl API WebGL extensions ~4 Supported GL features Navigator properties ~3 hardwareConcurrency, deviceMemory Color depth + touch support ~2 display.colorDepth, maxTouchPoints Add those up and you’re at roughly 54-63 bits of entropy. You need about 33 bits to uniquely identify someone in a pool of 8 billion. We’re at nearly double that. What Actually Defends Against This I tested four approaches on my fingerprinting test page. Here’s my honest assessment: Brave Browser — Best out-of-the-box protection. Brave randomizes Canvas and WebGL output on every session (adds noise to the rendered pixels). AudioContext gets similar treatment. In my tests, Brave generated a different fingerprint hash on every page load. The tradeoff: some sites that rely on Canvas for legitimate purposes (online editors, games) may behave slightly differently. I haven’t hit real issues with this in daily use. Firefox with privacy.resistFingerprinting — Setting privacy.resistFingerprinting = true in about:config spoofs many signals: timezone reports UTC, screen resolution reports the CSS viewport size, user-agent becomes generic. It’s effective but aggressive — it broke two web apps I use daily (a video editor and a mapping tool) because they relied on accurate screen dimensions. Tor Browser — The gold standard. Every Tor user presents an identical fingerprint by design. Canvas, WebGL, fonts, screen size — all normalized. But you’re trading performance (Tor routing adds 2-5x latency) and compatibility (many sites block Tor exit nodes). Browser extensions (Canvas Blocker, etc.) — Ironically, these can make you more unique. If 0.1% of users run Canvas Blocker, and it alters your fingerprint in a detectable way (which it does — the blocking itself is a signal), you’ve just moved from “one in a million” to “one in a thousand who runs this specific extension.” I stopped recommending these after seeing the data. A Practical Test You Can Run Right Now Visit Cover Your Tracks (EFF) — it runs a fingerprinting test and shows exactly how unique your browser is. Then visit BrowserLeaks.com for a more detailed breakdown of each signal. When I ran both tests in Chrome, my fingerprint was unique among their entire dataset. In Brave, it was shared with “a large number of users.” That difference matters. What Developers Should Do If you’re building analytics or auth systems, understand that fingerprinting exists in a gray area. The GDPR considers device fingerprints personal data (Article 29 Working Party Opinion 9/2014 explicitly says so). California’s CCPA covers “unique identifiers” whic


---
## Privacy-Focused Diff Checker: No Text Upload Required

- URL: https://orthogonal.info/difflab-private-diff-checker/
- Date: 2026-04-02
- Category: Tools & Setup
- Summary: Every popular online diff tool either uploads your text to a server or paywalls basic features. I built DiffLab: a single-file, zero-dependency diff checker that runs entirely in your browser with character-level highlighting and keyboard navigation.

I spent last weekend comparing two config files — a 400-line nginx setup where I’d made changes across multiple servers. I opened Diffchecker.com, pasted both files, and immediately ran into the same frustrations I’ve had for years: the page uploaded my text to their server (privacy issue for config files), there were no keyboard shortcuts to jump between changes, and the character-level highlighting was either nonexistent or buried behind a Pro paywall. So I built my own. The Problem with Every Online Diff Tool 📌 TL;DR: I spent last weekend comparing two config files — a 400-line nginx setup where I’d made changes across multiple servers. I opened Diffchecker. 🎯 Quick Answer: A privacy-focused diff checker runs entirely in your browser using client-side JavaScript—no text is ever uploaded to a server. This makes it safe for comparing proprietary code, credentials, or sensitive documents. Here’s what bugs me about existing text comparison tools. I tested the top three before writing a single line of code: Diffchecker.com — The default recommendation on every “best tools” list. It works, but your text gets sent to their servers. For comparing code, configs, or anything with credentials nearby, that’s a non-starter. They also paywall basic features like “find next difference” behind a $10/month subscription. TextDiffViewer.com — Claims client-side processing, which is good. But the UI feels like it was built in 2012. No character-level highlighting within changed lines, no unified diff view, no keyboard shortcuts. If I’m comparing 2,000 lines, I need to jump between changes, not scroll manually. DevToolLab’s Diff Checker — Clean UI, but limited. Only side-by-side view, no way to ignore whitespace or case differences, and no file drag-and-drop. Fine for small comparisons, frustrating for real work. The pattern is clear: either the tool uploads your data (privacy problem) or it’s missing features that developers actually need (navigation, views, options). It’s the same reason I built RegexLab — Regex101 sends your patterns to their server, which is a deal-breaker when you’re testing patterns against production data. What I Built: DiffLab DiffLab is a single HTML file — no server, no dependencies, no uploads. Everything runs in your browser. Close the tab and your data is gone. Here’s what makes it different: Three View Modes Most diff tools give you side-by-side and call it a day. DiffLab has three views you can switch between with keyboard shortcuts: Side-by-side (press 1) — The classic two-column layout. Changed lines are paired, and character-level differences are highlighted within each line so you can see exactly what changed. Unified (press 2) — Like git diff output. Removed lines appear with a - prefix, added lines with +. Compact and scannable. Inline (press 3) — Shows old → new on the same line with character highlighting. Best for reviewing small edits across many lines. Keyboard Navigation Between Changes This is the feature I wanted most. In a long file with changes scattered throughout, scrolling to find each difference is painful. DiffLab tracks every change “hunk” and lets you: Press J or ↓ to jump to the next change Press K or ↑ to jump to the previous change A floating indicator shows “Change 3 of 12” so you always know where you are The current change gets a visible highlight so your eye can find it instantly after scrolling. Character-Level Highlighting When a line changes, DiffLab doesn’t just highlight the whole line red/green. It finds the common prefix and suffix of the old and new line, then highlights only the characters that actually changed. If you renamed getUserById to getUserByName, only Id and Name get highlighted — not the entire function call. Comparison Options Two toggles that matter for real-world comparisons: Ignore whitespace (W) — Treats a b and a b as identical. Essential for comparing code with different formatting. Ignore case (C) — Treats Hello and hello as identical. Useful for config files where case doesn’t matter. Both options re-run the diff instantly when toggled. How It Works Under the Hood The Diff Algorithm DiffLab uses a Myers diff algorithm — the same algorithm behind git diff. It finds the shortest edit script (minimum number of insertions and deletions) to transform the original text into the modified text. For inputs under 8,000 total lines, it runs the full Myers algorithm. For larger inputs, it switches to a faster LCS-based approach with lookahead that trades perfect minimality for speed. In practice, both produce identical results for typical comparisons. The algorithm runs in your browser’s main thread. On my laptop, comparing two 5,000-line files takes about 40ms. The rendering is the bottleneck, not the diff calculation. Character Diffing For paired changed lines (a removed line followed by an added line at the same position), DiffLab runs a second pass. It finds the longest common prefix and suffix of the two strings, then wraps the changed middle section in highlight spans. This is simpler than running a full diff on individual characters, but it’s fast and catches the common case (a word or variable name changed in the middle of a line) perfectly. Rendering Strategy The diff output renders as an HTML table. Each row is a line, with cells for line numbers and content. I chose tables over divs because: Line numbers stay aligned with content automatically Column widths distribute properly in side-by-side mode Screen readers can navigate the structure Changed lines get CSS classes (diff-added, diff-removed) that map to CSS custom properties. Dark mode support comes free through prefers-color-scheme media queries — the custom properties switch automatically. Real Use Cases I’ve been using DiffLab daily since building it. Here are the situations where it’s genuinely useful: Comparing deployment configs — Before pushing a staging config to production, paste both and verify only the expected values changed. Code review diffs — When a PR is too large for GitHub’s diff view, copy-paste specific files for focused comparison. Database migration scripts — Compare the current schema dump with the new one to make sure nothing got dropped accidentally. Documentation updates — Writers comparing draft versions to see what an editor changed. API response debugging — Compare expected vs actual JSON responses. The character-level highlighting catches subtle differences in values. For formatting those JSON responses first, JSON Forge does the same client-side-only trick. Privacy and Offline Use DiffLab includes a service worker that caches everything on first visit. After that, it works completely offline — airplane mode, no internet, whatever. Your text never leaves the browser tab, and there’s no analytics tracking what you compare. It also passes Chrome’s PWA install requirements. Click “Install” in your browser’s address bar and it becomes a standalone app on your desktop or phone. Try It DiffLab is live at difflab.orthogonal.info. Single HTML file, zero dependencies, works offline. The keyboard shortcuts are the selling point: Ctrl+Enter to compare, J/K to navigate changes, 1/2/3 to switch views, W for whitespace, C for case, S to swap sides, Escape to clear. If you compare text files regularly, give it a try. DiffLab is part of a growing set of free browser tools that replace desktop apps — all client-side, all offline-capable. If you build developer tools, I’d love to hear what’s missing — reach out at [email protected]. If you spend hours comparing code and config files, a good ergonomic keyboard makes the typing between comparisons much more comfortable. I switched to a split keyboard last year and my wrists thank me daily. For monitor setups that make side-by-side diffs actually readable, check out these ultrawide monitors for programming — the extra horizontal space is worth it. And if you’re doing serious code reviews, a monitor arm to position your screen at the right height reduces neck str


---
## Citrix NetScaler CVE-2026-3055 Exploited: What to Do Now

- URL: https://orthogonal.info/cve-2026-3055-citrix-netscaler-memory-leak-is-being-exploited-right-now-here-is-what-to-do/
- Date: 2026-04-02
- Category: Deep Dives
- Summary: CVE-2026-3055 is a CVSS 9.3 memory overread in Citrix NetScaler ADC and Gateway. Attackers are already using it to steal admin session tokens via crafted SAML requests. CISA deadline is today. Here is what to check and how to fix it.

Last Wednesday I woke 🔧 From my experience: After CitrixBleed, I started running automated config diffs against known-good baselines on a daily cron. It’s a 10-line bash script that’s caught unauthorized changes twice. Don’t wait for the next CVE to build that habit. up to three Slack messages from different clients, all asking the same thing: “Is our NetScaler safe?” A new Citrix vulnerability had dropped — CVE-2026-3055 — and by Saturday, CISA had already added it to the Known Exploited Vulnerabilities catalog. That’s a 7-day turnaround from disclosure to confirmed in-the-wild exploitation. If you’re running NetScaler ADC or NetScaler Gateway with SAML configured, stop what you’re doing and patch. What CVE-2026-3055 Actually Does 📌 TL;DR: Last Wednesday I woke up to three Slack messages from different clients, all asking the same thing: “Is our NetScaler safe?” A new Citrix vulnerability had dropped — CVE-2026-3055 — and by Saturday, CISA had already added it to the Known Exploited Vulnerabilities catalog. 🎯 Quick Answer: CVE-2026-3055 is a critical Citrix NetScaler vulnerability actively exploited in the wild. Patch immediately to the latest NetScaler firmware; if patching is delayed, block external access to the management interface and monitor for indicators of compromise. CVE-2026-3055 is an out-of-bounds memory read in Citrix NetScaler ADC and NetScaler Gateway. CVSS 9.3. An unauthenticated attacker sends a crafted request to your SAML endpoint, and your appliance responds by dumping chunks of its memory — including admin session tokens. If that sounds familiar, it should. This is the same class of bug that plagued CitrixBleed (CVE-2023-4966) — one of the most exploited vulnerabilities of 2023. The security community is already calling this one “CitrixBleed 3.0,” and I think that’s fair. The researchers at watchTowr Labs found that CVE-2026-3055 actually covers two separate memory overread bugs, not one: /saml/login — Attackers send a SAMLRequest payload that omits the AssertionConsumerServiceURL field. The appliance leaks memory contents via the NSC_TASS cookie. /wsfed/passive — A request with a wctx query parameter present but without a value (no = sign) causes the appliance to read from dead memory. The data comes back Base64-encoded in the same NSC_TASS cookie, but without the size limits of the SAML variant. In both cases, the leaked memory can contain authenticated session IDs. Grab one of those, and you’ve got full admin access to the appliance. No credentials needed. The Timeline Is Ugly March 23, 2026 — Citrix publishes security bulletin CTX696300 disclosing the flaw. They describe it as an internal security review finding. March 27 — watchTowr’s honeypot network detects active exploitation from known threat actor IPs. Defused Cyber observes attackers probing /cgi/GetAuthMethods to fingerprint which appliances have SAML enabled. March 29 — watchTowr publishes a full technical analysis and releases a Python detection script. March 30 — CISA adds CVE-2026-3055 to the KEV catalog. Rapid7 releases a Metasploit module. April 2 — CISA’s deadline for federal agencies to patch or discontinue use. That’s today. Four days from disclosure to active exploitation. Six days to a public Metasploit module. This is about as bad as the timeline gets. Are You Vulnerable? You’re affected if you run on-premise NetScaler ADC or NetScaler Gateway with SAML Identity Provider configured. Cloud-managed instances (Citrix-hosted) are not affected. Check your NetScaler config for this string: add authentication samlIdPProfile If that line exists in your config, you need to patch. If you use SAML SSO through your NetScaler — and plenty of enterprises do — assume you’re in scope. Affected versions: NetScaler ADC and Gateway 14.1 before 14.1-66.59 NetScaler ADC and Gateway 13.1 before 13.1-62.23 NetScaler ADC 13.1-FIPS before 13.1-37.262 NetScaler ADC 13.1-NDcPP before 13.1-37.262 The Exposure Numbers The Shadowserver Foundation counted roughly 29,000 NetScaler ADC instances and 2,250 Gateway instances visible on the internet as of March 28. Not all of those are necessarily running SAML, but the attackers already have an automated way to check — that /cgi/GetAuthMethods fingerprinting technique Defused Cyber spotted. A quick Shodan check shows the US, Germany, and the UK have the highest exposure counts. If you’re running NetScaler in any of those regions, you’re likely already being probed. What watchTowr Calls “Disingenuous” This is the part that bothers me. Citrix’s original security bulletin didn’t mention that the flaw was being actively exploited. It described CVE-2026-3055 as a single vulnerability found through “ongoing security reviews.” watchTowr’s analysis showed it was actually two distinct bugs bundled under one CVE, and the disclosure was incomplete about the attack surface. watchTowr explicitly called the disclosure “disingenuous.” I tend to agree. When your customers are running edge appliances that handle authentication for their entire organization, underplaying the severity of a memory leak bug — especially one with clear echoes of CitrixBleed — isn’t great. Patch Now — Here Are the Fixed Versions Upgrade to these versions or later: ProductFixed Version NetScaler ADC & Gateway 14.114.1-66.59 NetScaler ADC & Gateway 13.113.1-62.23 NetScaler ADC 13.1-FIPS13.1-37.262 NetScaler ADC 13.1-NDcPP13.1-37.262 If you can’t patch immediately, at minimum disable the SAML IDP profile until you can. But really — patch. Disabling SAML probably breaks your SSO, and your users will notice. Patching and rebooting during a maintenance window is the better path. Post-Patch: Check for Compromise Patching alone isn’t enough if attackers already hit your appliance. Here’s what I’d check: Review session logs — Look for unusual admin sessions, especially from IP ranges that don’t match your admin team. Rotate admin credentials — If session tokens leaked, changing passwords invalidates stolen sessions. Check for persistence — Past CitrixBleed campaigns dropped web shells and created backdoor accounts. Run a full config diff against a known-good backup. Inspect NSC_TASS cookies in access logs — Unusually large Base64 values in this cookie are a red flag. Use watchTowr’s detection script — They published a Python tool specifically for identifying vulnerable instances. Run it against your fleet. Why This Pattern Keeps Repeating This is the third major Citrix memory leak vulnerability in three years (CitrixBleed in 2023, CitrixBleed2 in 2025, now CVE-2026-3055 in 2026). Each time, the exploitation timeline gets shorter. CitrixBleed took weeks before widespread exploitation. This one took four days. The problem is structural: NetScaler sits at the network edge, handles authentication, and touches sensitive data by design. A memory leak in an edge appliance is categorically worse than one in an internal service because the attack surface is the public internet. If you’re running edge appliances from any vendor, you need a patching process that can turn around critical updates in under 48 hours. Not weeks. Not “the next maintenance window.” Resources Here are the reference books I keep on my desk for situations exactly like this: Network Security Assessment by Chris McNab — the go-to for understanding how attackers probe network appliances. The chapter on SAML/SSO attack surfaces is worth reading right now. (Full disclosure: affiliate link) Hacking Exposed 7 by McClure, Scambray, Kurtz — if you want to understand the attacker’s perspective on edge infrastructure exploitation, this is the classic. (Affiliate link) Practical Cloud Security by Chris Dotson — good coverage of identity federation and why SAML misconfigurations create exploitable gaps. (Affiliate link) For hardware-level defense, I’m a fan of YubiKey 5C NFC for hardening admin access. Even if an attacker steals a session token, hardware-backed MFA on your admin accounts adds a second layer they can’t bypass remotely. 


---
## Git Worktrees: The Feature That Killed My Stash Habit

- URL: https://orthogonal.info/git-worktrees-the-feature-that-killed-my-stash-habit/
- Date: 2026-04-02
- Category: Tools & Setup
- Summary: Git worktrees let you check out multiple branches in separate directories. Here’s how I use them for code reviews, hotfixes, and parallel work.

Last Tuesday I was deep in a refactor — 40 files touched, tests half-green — when Slack lit up: “Production’s returning 500s, can you look at main?” My old workflow: git stash, switch branches, forget what I stashed, lose 20 minutes reconstructing state. My current workflow: git worktree add ../hotfix main, fix the bug in a separate directory, push, delete the worktree, and I never left my refactor. Total context-switch cost: zero. Git worktrees have been around since Git 2.5 (July 2015), but I’ve met maybe three developers who actually use them. Everyone else is still juggling git stash or cloning the repo twice. That’s a shame, because worktrees solve a real, daily problem — and they’re already on your machine. What Git Worktrees Actually Are 📌 TL;DR: Git worktrees allow developers to manage multiple branches or commits in separate directories without duplicating the repository. This feature eliminates the need for git stash during context switches, saving time and reducing cognitive load, especially for tasks like code reviews and hotfixes. 🎯 Quick Answer: Git worktrees let you check out multiple branches simultaneously in separate directories, eliminating the need for git stash. Run git worktree add ../feature-branch feature-branch to work on two branches without switching or stashing. A worktree is a linked checkout of your repository at a different branch or commit, in a separate directory, sharing the same .git data. One repo, multiple working directories, each on its own branch. No duplicate clones, no extra disk space for the object database. # You're on feature/auth-refactor in ~/code/myapp git worktree add ../myapp-hotfix main # Now ~/code/myapp-hotfix has a full checkout of main # Both share the same .git/objects — no duplicate data The key constraint: each worktree must be on a different branch. Git won’t let two worktrees check out the same branch simultaneously (that would be a recipe for corruption). This is actually a feature — it forces clean separation. Three Workflows Where Worktrees Save Real Time 1. Code Reviews Without Context Switching I review 3-5 PRs a day. Before worktrees, reviewing meant either reading diffs in the GitHub UI (fine for small changes, terrible for architectural PRs) or stashing my work to check out the PR branch locally. Now I keep a persistent review worktree: # One-time setup git worktree add ../review main # When a PR comes in cd ../review git fetch origin git checkout origin/feature/new-payment-flow # Run tests, inspect code, check behavior npm test npm run dev # boot the app on a different port # Done reviewing? Back to my branch cd ../myapp # My work is exactly where I left it The time savings are measurable. I tracked it for two weeks: stash-and-switch averaged 4 minutes per review (stash, checkout, install deps, run, switch back, pop stash). Worktrees averaged 40 seconds. With 4 reviews a day, that’s ~13 minutes saved daily. Not life-changing alone, but it compounds — and the cognitive cost of interrupted focus is way higher than the clock time. 2. Hotfixes While Mid-Feature This is the classic case. You’re mid-feature, things are broken in your working tree (intentionally — you’re refactoring), and production needs a patch. With worktrees: git worktree add ../hotfix main cd ../hotfix # fix the bug git commit -am "fix: null check on user.profile (#1234)" git push origin main cd ../myapp git worktree remove ../hotfix No stash. No “was I on the right commit?” No dependency reinstall because your lockfile changed between branches. Clean in, clean out. 3. Running Two Versions Side-by-Side I needed to compare API response times between our v2 and v3 endpoints during a migration. With worktrees, I had both versions running simultaneously on different ports: git worktree add ../api-v2 release/v2 cd ../api-v2 && PORT=3001 npm start & cd ../myapp && PORT=3002 npm start & # Now hit both with curl or your HTTP client curl -w "%{time_total}\n" http://localhost:3001/api/users curl -w "%{time_total}\n" http://localhost:3002/api/users Try doing that with stash. You can’t. The Commands You Actually Need The full git worktree subcommand has plenty of options, but in practice I use four: # Create a worktree for an existing branch git worktree add ../path branch-name # Create a worktree with a new branch (like checkout -b) git worktree add -b new-branch ../path starting-point # List all worktrees git worktree list # Remove a worktree (cleans up the directory and git references) git worktree remove ../path That’s it. Four commands cover 95% of usage. There’s also git worktree prune for cleaning up stale references if you manually delete a worktree directory instead of using remove, but you shouldn’t need it often. Gotchas I Hit (So You Don’t Have To) Node modules and build artifacts. Each worktree is a separate directory, so you need a separate node_modules in each. For a big project, that first npm install in a new worktree takes time. I mitigate this by keeping one long-lived review worktree rather than creating/destroying them constantly. IDE confusion. VS Code handles worktrees well — just open the worktree directory as a separate window. JetBrains IDEs can get confused if you open the same project root with different worktrees. The fix: open the specific worktree directory, not the parent. Submodules. If your repo uses submodules, you need to run git submodule update --init in each new worktree. Worktrees don’t automatically initialize submodules. Annoying, but a one-liner fix. Branch locking. Remember: one branch per worktree. If you try to check out a branch that’s already active in another worktree, Git blocks you with: fatal: 'main' is already checked out at '/home/user/code/myapp-hotfix' This is intentional and correct. If you need to work on the same branch from two directories, you have a workflow problem, not a Git problem. My Worktree Setup I keep my projects structured like this: ~/code/ ├── myapp/ # main development (feature branches) ├── myapp-review/ # persistent review worktree (long-lived) └── myapp-hotfix/ # created on-demand, deleted after use I added a shell alias to speed up the common case: # ~/.zshrc or ~/.bashrc hotfix() { local branch="${1:-main}" local dir="../$(basename $(pwd))-hotfix" git worktree add "$dir" "$branch" && cd "$dir" } # Usage: just type 'hotfix' or 'hotfix release/v3' And a cleanup alias: worktree-clean() { git worktree list --porcelain | grep "^worktree" | awk '{print $2}' | while read wt; do if [ "$wt" != "$(git rev-parse --show-toplevel)" ]; then echo "Remove $wt? (y/n)" read answer [ "$answer" = "y" ] && git worktree remove "$wt" fi done } When Worktrees Aren’t the Answer I’m not going to pretend worktrees solve everything. They don’t make sense when: You’re working solo on a single branch. No context switching means no need for worktrees. Your project has a 10-minute build step. Each worktree needs its own build, so the overhead might not be worth it for infrequent switches. You need the same branch in two places. Worktrees explicitly prevent this. Clone the repo instead. For everything else — code reviews, hotfixes, comparing versions, running multiple branches in parallel — worktrees are the right tool. I’ve been using them daily for about a year now, and git stash usage in my shell history dropped from ~15 times/week to maybe once. Level Up Your Git Setup If you’re spending real time on Git workflows, it’s worth investing in a proper reference. Pro Git by Scott Chacon covers worktrees alongside the internals that make them possible — understanding Git’s object model makes everything click. (Full disclosure: affiliate link.) For the terminal setup that makes all this fast, a solid mechanical keyboard actually matters when you’re typing Git commands dozens of times a day. I’ve been using a Keychron Q1 — the tactile feedback on those Brown switches makes a difference over an 8-hour session. (Affiliate link.) And if you want more developer workflow content, I write ab


---
## TrueNAS Setup Guide: Enterprise Security for Your Homelab

- URL: https://orthogonal.info/truenas-setup-guide-enterprise-security-homelab/
- Date: 2026-04-01
- Category: Uncategorized
- Summary: Last month I rebuilt my TrueNAS server from scratch after a drive failure. What started as a simple disk replacement turned into a full security audit — and I realized my homelab storage had been running with basically no access controls, no encryption, and SSH root login enabled. Not great. Here’s 

Last month I rebuilt my TrueNAS server from scratch after a drive failure. What started as a simple disk replacement turned into a full security audit — and I realized my homelab storage had been running with basically no access controls, no encryption, and SSH root login enabled. Not great. Here’s how I set up TrueNAS SCALE with actual security practices borrowed from enterprise environments — without the enterprise complexity. Why TrueNAS for Homelab Storage 📌 TL;DR: This guide explains how to set up a secure TrueNAS SCALE system for a homelab, incorporating enterprise-grade practices like ZFS snapshots, ECC RAM, VLAN network isolation, and dataset encryption. It emphasizes critical hardware choices and network configurations to protect data integrity and prevent unauthorized access. 🎯 Quick Answer: Secure a TrueNAS SCALE homelab by enabling ZFS dataset encryption, using ECC RAM to prevent silent data corruption, isolating services with VLANs, and scheduling automatic ZFS snapshots for rollback protection. TrueNAS runs on ZFS, which handles data integrity better than anything else I’ve used at home. The killer features for me: ZFS snapshots — I accidentally deleted an entire media folder last year. Restored it in 30 seconds from a snapshot. That alone justified the setup. Built-in checksumming — ZFS detects and repairs silent data corruption (bit rot). Your photos from 2015 will still be intact in 2035. Replication — automated offsite backups over encrypted channels. I went with TrueNAS SCALE over Core because I wanted Linux underneath — it lets me run Docker containers (Plex, Home Assistant, Nextcloud) alongside the storage. If you don’t need containers, Core on FreeBSD works fine too. Hardware: What Actually Matters You don’t need server-grade hardware, but a few things are non-negotiable: ECC RAM — ZFS benefits enormously from error-correcting memory. I run 32GB of ECC. If your board supports it, use it. 16GB is the minimum for ZFS caching to work well. CPU with AES-NI — any modern AMD Ryzen or Intel chip has this. You need it for dataset encryption without tanking performance. NAS-rated drives — I run WD Red Plus 8TB drives in RAID-Z1. Consumer drives aren’t designed for 24/7 operation and will fail faster. CMR (not SMR) matters here. A UPS — ZFS hates unexpected power loss. An APC 1500VA UPS with NUT integration gives you automatic clean shutdowns. I wrote about setting up NUT on TrueNAS separately. My current build: AMD Ryzen 5 5600G, 32GB Crucial ECC SODIMM, three 8TB WD Reds in RAID-Z1, and a 500GB NVMe as SLOG cache. Total cost around $800 — not cheap, but cheaper than losing irreplaceable data. Network Isolation First Before you even install TrueNAS, get your network right. Your NAS has all your data on it — it shouldn’t sit on the same flat network as your kids’ tablets and smart bulbs. I use OPNsense with VLANs to isolate my homelab. The NAS lives on VLAN 10, IoT devices on VLAN 30, and my workstation has cross-VLAN access via firewall rules. If an IoT device gets compromised (and they will eventually), it can’t reach my storage. The firewall rule is simple — only allow specific subnets to hit the TrueNAS web UI on port 443: # OPNsense/pfSense rule example pass in on vlan10 proto tcp from 192.168.10.0/24 to 192.168.10.100 port 443 If you’re running a Protectli Vault or similar appliance for your firewall, this takes maybe 20 minutes to set up. No excuses. Installation and Initial Lockdown The install itself is straightforward — download the ISO, flash a USB with Etcher, boot, follow the wizard. Use a separate SSD or USB for the boot device; don’t waste pool drives on the OS. Once you’re in the web UI, immediately: Change the admin password to something generated by your password manager. Not “admin123”. Enable 2FA — TrueNAS supports TOTP. Set it up before you do anything else. Disable SSH root login: # In /etc/ssh/sshd_config PermitRootLogin no Create a non-root user for SSH access instead. I use key-based auth only — password SSH is disabled entirely. Create Your Storage Pool # RAID-Z1 with three drives zpool create mypool raidz1 /dev/sda /dev/sdb /dev/sdc RAID-Z1 gives you one drive of redundancy. For more critical data, RAID-Z2 (two-drive redundancy) is worth the capacity trade-off. I run Z1 because I replicate offsite daily — the real backup is the replication, not the RAID. Enterprise Security Practices, Scaled Down Access Controls That Actually Work Don’t give everyone admin access. Create separate users with specific dataset permissions: # Create a limited user for media access adduser --home /mnt/mypool/media --shell /bin/bash mediauser chmod 750 /mnt/mypool/media My wife has read-only access to the photo datasets. The kids’ Plex account can only read the media dataset. Nobody except my admin account can touch the backup datasets. This takes 10 minutes to set up and prevents the “oops I deleted everything” scenario. Encrypt Sensitive Datasets TrueNAS makes encryption easy — you enable it during dataset creation. I encrypt anything with personal documents, financial records, or credentials. The performance hit with AES-NI hardware is negligible (under 5% in my benchmarks). For offsite backups, I use rsync over SSH with forced encryption: # Encrypted backup to remote server rsync -avz --progress -e "ssh -i ~/.ssh/backup_key" \ /mnt/mypool/critical/ backup@remote:/mnt/backup/ VPN for Remote Access Never expose your TrueNAS web UI to the internet. I use WireGuard through OPNsense — when I need to check on things remotely, I VPN in first. The firewall blocks everything else. I covered secure remote access patterns in detail before. Ongoing Maintenance Setup is maybe 20% of the work. The rest is keeping it running reliably: ZFS scrubs — I run weekly scrubs on Sunday nights. They catch silent corruption before it becomes a problem. Schedule this in the TrueNAS UI under Tasks → Scrub Tasks. Updates — check for TrueNAS updates monthly. Don’t auto-update a NAS; read the release notes first. Monitoring — I pipe TrueNAS metrics into Grafana via Prometheus. SMART data, pool health, CPU/RAM usage. When a drive starts showing pre-failure indicators, I know before it dies. Snapshot rotation — keep hourly snapshots for 48 hours, daily for 30 days, weekly for 6 months. Automate this in the TrueNAS snapshot policies. Test your backups. Seriously. I do a full restore test every quarter — pull a snapshot, restore it to a test dataset, verify the files are intact. An untested backup is not a backup. Where to Go From Here Once your TrueNAS box is running securely, you can start adding services. I run Plex, Nextcloud, Home Assistant, and a Gitea instance all on the same SCALE box using Docker. Each service gets its own dataset with isolated permissions. If you want to go deeper on the networking side, I’d start with full network segmentation with OPNsense. For monitoring, check out my post on open-source security monitoring. Frequently Asked Questions Why choose TrueNAS for a homelab? TrueNAS uses ZFS, which offers superior data integrity features like snapshots, checksumming, and automated replication. It also supports additional functionality like Docker containers on TrueNAS SCALE. What hardware is recommended for TrueNAS? Key recommendations include ECC RAM (16GB minimum), a CPU with AES-NI for encryption, NAS-rated drives (e.g., WD Red Plus), and a UPS to prevent data corruption during power loss. How can I secure my TrueNAS setup? Use VLANs to isolate your NAS from other devices, configure strict firewall rules, disable root SSH login, and enable dataset encryption. These steps help protect your data from unauthorized access and potential network threats. What are the benefits of ZFS in TrueNAS? ZFS provides features like snapshots for quick data recovery, built-in checksumming to prevent silent data corruption, and replication for secure offsite backups. References TrueNAS Documentation — “TrueNAS SCALE User Guide” OpenZFS — “ZFS Overview and 


---
## UPS Battery Backup: Sizing, Setup & NUT on TrueNAS

- URL: https://orthogonal.info/ups-battery-backup-for-your-homelab-sizing-setup-and-nut-automatic-shutdown-on-truenas/
- Date: 2026-04-01
- Category: Homelab
- Summary: A half-second power flicker corrupted my ZFS pool mid-scrub. Here’s how I picked a UPS, sized it for my homelab, and configured NUT on TrueNAS for automatic graceful shutdown.

Last month my TrueNAS server rebooted mid-scrub during a power flicker that lasted maybe half a second. Nothing dramatic — the lights barely dimmed — but the ZFS pool came back with a degraded vdev and I spent two hours rebuilding. That’s when I finally stopped procrastinating and bought a UPS. If you’re running a homelab with any kind of persistent storage — especially ZFS on TrueNAS — you need battery backup. Not “eventually.” Now. Here’s what I learned picking one out and setting it up with automatic shutdown via NUT. Why Homelabs Need a UPS More Than Desktops Do 📌 TL;DR: A UPS battery backup is essential for homelabs running persistent storage like TrueNAS to prevent data corruption during power outages. Pure sine wave UPS units are recommended for modern server PSUs with active PFC, ensuring compatibility and reliable operation. The article discusses UPS selection, setup, and integration with NUT for automatic shutdown during outages. 🎯 Quick Answer: Size a UPS at 1.5× your homelab’s measured wattage, choose pure sine wave output to protect server PSUs, and configure NUT (Network UPS Tools) on TrueNAS to trigger automatic shutdown before battery depletion. A desktop PC losing power is annoying. You lose your unsaved work and reboot. A server losing power mid-write can corrupt your filesystem, break a RAID rebuild, or — in the worst case with ZFS — leave your pool in an unrecoverable state. I’ve been running TrueNAS on a custom build (I wrote about picking the right drives for it) and the one thing I kept putting off was power protection. Classic homelab mistake: spend $800 on drives, $0 on keeping them alive during outages. The math is simple. A decent UPS costs $150-250. A failed ZFS pool can mean rebuilding from backup (hours) or losing data (priceless). The UPS pays for itself the first time your power blips. Simulated Sine Wave vs. Pure Sine Wave — It Actually Matters Most cheap UPS units output a “simulated” or “stepped” sine wave. For basic electronics, this is fine. But modern server PSUs with active PFC (Power Factor Correction) can behave badly on simulated sine wave — they may refuse to switch to battery, reboot anyway, or run hot. The rule: if your server has an active PFC power supply (most ATX PSUs sold after 2020 do), get a pure sine wave UPS. Don’t save $40 on a simulated unit and then wonder why your server still crashes during outages. Both units I’d recommend output pure sine wave: APC Back-UPS Pro BR1500MS2 — My Pick This is what I ended up buying. The APC BR1500MS2 is a 1500VA/900W pure sine wave unit with 10 outlets, USB-A and USB-C charging ports, and — critically — a USB data port for NUT monitoring. (Full disclosure: affiliate link.) Why I picked it: Pure sine wave output — no PFC compatibility issues USB HID interface — TrueNAS recognizes it immediately via NUT, no drivers needed 900W actual capacity — enough for my TrueNAS box (draws ~180W), plus my network switch and router LCD display — shows load %, battery %, estimated runtime in real-time User-replaceable battery — when the battery dies in 3-5 years, swap it for ~$40 instead of buying a new UPS At ~180W load, I get about 25 minutes of runtime. That’s more than enough for NUT to detect the outage and trigger a clean shutdown. CyberPower CP1500PFCLCD — The Alternative If APC is out of stock or you prefer CyberPower, the CP1500PFCLCD is the direct competitor. Same 1500VA rating, pure sine wave, 12 outlets, USB HID for NUT. (Affiliate link.) The CyberPower is usually $10-20 cheaper than the APC. Functionally, they’re nearly identical for homelab use. I went APC because I’ve had good luck with their battery replacements, but either is a solid choice. Pick whichever is cheaper when you’re shopping. Sizing Your UPS: VA, Watts, and Runtime UPS capacity is rated in VA (Volt-Amps) and Watts. They’re not the same thing. For homelab purposes, focus on Watts. Here’s how to size it: Measure your actual draw. A Kill A Watt meter costs ~$25 and tells you exactly how many watts your server pulls from the wall. (Affiliate link.) Don’t guess — PSU wattage ratings are maximums, not actual draw. Add up everything you want on battery. Server + router + switch is typical. Monitors and non-essential stuff go on surge-only outlets. Target 50-70% load. A 900W UPS running 450W of gear gives you reasonable runtime (~8-12 minutes) and doesn’t stress the battery. My setup: TrueNAS box (~180W) + UniFi switch (~15W) + router (~12W) = ~207W total. On a 900W UPS, that’s 23% load, giving me ~25 minutes of runtime. Overkill? Maybe. But I’d rather have headroom than run at 80% and get 4 minutes of battery. Setting Up NUT on TrueNAS for Automatic Shutdown A UPS without automatic shutdown is just a really expensive power strip with a battery. The whole point is graceful shutdown — your server detects the outage, saves everything, and powers down cleanly before the battery dies. TrueNAS has NUT (Network UPS Tools) built in. Here’s the setup: 1. Connect the USB data cable Plug the USB cable from the UPS into your TrueNAS machine. Not a charging cable — the data cable that came with the UPS. Go to System → Advanced → Storage and make sure the USB device shows up. 2. Configure the UPS service In TrueNAS SCALE, go to System Settings → Services → UPS: UPS Mode: Master Driver: usbhid-ups (auto-detected for APC and CyberPower) Port: auto Shutdown Mode: UPS reaches low battery Shutdown Timer: 30 seconds Monitor User: upsmon Monitor Password: (set something, you'll need it for NUT clients) 3. Enable and test Start the UPS service, enable auto-start. Then SSH in and check: upsc ups@localhost You should see battery charge, load, input voltage, and status. If it says OL (online), you’re good. Pull the power cord from the wall briefly — it should switch to OB (on battery) and you’ll see the charge start to drop. 4. NUT clients for other machines If you’re running Docker containers or other servers (like an Ollama inference box), they can connect as NUT clients to the same UPS. On a Linux box: apt install nut-client # Edit /etc/nut/upsmon.conf: MONITOR ups@truenas-ip 1 upsmon yourpassword slave SHUTDOWNCMD "/sbin/shutdown -h +0" Now when the UPS battery hits critical, TrueNAS shuts down first, then signals clients to do the same. Monitoring UPS Health Over Time Batteries degrade. A 3-year-old UPS might only give you 8 minutes instead of 25. NUT tracks battery health, but you need to actually look at it. I have a cron job that checks upsc ups@localhost battery.charge weekly and logs it. If charge drops below 80% at full load, it’s time for a replacement battery. APC replacement batteries (RBC models) run $30-50 on Amazon and take two minutes to swap. If you’re running a monitoring stack (Prometheus + Grafana), there’s a NUT exporter that makes this trivial. But honestly, a cron job and a log file works fine for a homelab. What About Rack-Mount UPS? If you’ve graduated to a proper server rack, the tower units I mentioned above won’t fit. The APC SMT1500RM2U is the rack-mount equivalent — 2U, 1500VA, pure sine wave, NUT compatible. It’s about 2x the price of the tower version. Only worth it if you actually have a rack. For most homelabbers running a Docker or K8s setup on a single tower server, the desktop UPS units are plenty. Don’t buy rack-mount gear for a shelf setup — you’re paying for the form factor, not better protection. The Backup Chain: UPS Is Just One Link A UPS protects against power loss. It doesn’t protect against drive failure, ransomware, or accidental rm -rf. If you haven’t set up a real backup strategy, I wrote about enterprise-grade backup for homelabs — the 3-2-1 rule still applies, even at home. The full resilience stack for a homelab: UPS for power → ZFS for disk redundancy → offsite backups for disaster recovery. Skip any layer and you’re gambling. Go buy a UPS. Your data will thank you the next time the power blinks. Free market intelligence for traders and builders: Join Alpha Signal on Telegr


---
## Insider Trading Detector with Python & Free SEC Data

- URL: https://orthogonal.info/build-an-insider-trading-cluster-detector-with-python-and-free-sec-data/
- Date: 2026-04-01
- Category: Finance & Trading
- Summary: Detect insider buying clusters from SEC EDGAR Form 4 filings using Python and edgartools. Free, no API key. Complete working code included.

Last month I noticed something odd. Three directors at a mid-cap biotech quietly bought shares within a five-day window — all open-market purchases, no option exercises. The stock was down 30% from its high. Two weeks later, they announced a partnership with Pfizer and the stock popped 40%. I didn’t catch it in real time. I found it afterward while manually scrolling through SEC filings. That annoyed me enough to build a tool that would catch the next one automatically. Here’s the thing about insider buying clusters: they’re one of the few signals with actual academic backing. A 2024 study from the Journal of Financial Economics found that stocks with three or more insider purchases within 30 days outperformed the market by an average of 8.7% over the following six months. Not every cluster leads to a win, but the hit rate is better than most technical indicators I’ve tested. The data is completely free. Every insider trade gets filed with the SEC as a Form 4, and the SEC makes all of it available through their EDGAR API — no API key, no rate limits worth worrying about (10 requests/second), no paywall. The only catch: the raw data is XML soup. That’s where edgartools comes in. What Counts as a “Cluster” 📌 TL;DR: The article discusses using Python and free SEC EDGAR data to detect insider trading clusters, which are strong market signals backed by academic research. It introduces the ‘edgartools’ library to parse SEC filings and provides a script to identify clusters of significant insider purchases within a 30-day window. 🎯 Quick Answer: Detect insider trading clusters using Python and free SEC EDGAR Form 4 data. Flag stocks where 3+ insiders buy within a 14-day window—historically, clustered insider purchases outperform the market by 7–10% annually. Before writing code, I needed to define what I was actually looking for. Not all insider buying is equal. Strong signals: Open market purchases (transaction code P) — the insider spent their own money Multiple different insiders buying within a 30-day window Purchases by C-suite (CEO, CFO, COO) or directors — not mid-level VPs exercising options Purchases larger than $50,000 — skin in the game matters Weak signals (I filter these out): Option exercises (code M) — often automatic, not conviction Gifts (code G) — tax planning, not bullish intent Small purchases under $10,000 — could be a director fulfilling a minimum ownership requirement Setting Up the Python Environment You need exactly two packages: pip install edgartools pandas edgartools is an open-source Python library that wraps the SEC EDGAR API and parses the XML filings into clean Python objects. No API key required. It handles rate limiting, caching, and the various quirks of EDGAR’s data format. I’ve been using it for about six months and it’s saved me from writing a lot of painful XML parsing code. Here’s the core detection script: from edgar import Company, get_filings from datetime import datetime, timedelta from collections import defaultdict import pandas as pd def detect_insider_clusters(tickers, lookback_days=60, min_insiders=2, min_value=50000): # Scan a list of tickers for insider buying clusters. # A cluster = multiple different insiders making open-market # purchases within a rolling 30-day window. clusters = [] for ticker in tickers: try: company = Company(ticker) filings = company.get_filings(form="4") purchases = [] for filing in filings.head(50): form4 = filing.obj() for txn in form4.transactions: if txn.transaction_code != 'P': continue value = (txn.shares or 0) * (txn.price_per_share or 0) if value < min_value: continue purchases.append({ 'ticker': ticker, 'date': txn.transaction_date, 'insider': form4.reporting_owner_name, 'relationship': form4.reporting_owner_relationship, 'shares': txn.shares, 'price': txn.price_per_share, 'value': value }) if len(purchases) < min_insiders: continue df = pd.DataFrame(purchases) df['date'] = pd.to_datetime(df['date']) df = df.sort_values('date') cutoff = datetime.now() - timedelta(days=lookback_days) recent = df[df['date'] >= cutoff] if len(recent) == 0: continue unique_insiders = recent['insider'].nunique() if unique_insiders >= min_insiders: total_value = recent['value'].sum() clusters.append({ 'ticker': ticker, 'insiders': unique_insiders, 'total_purchases': len(recent), 'total_value': total_value, 'earliest': recent['date'].min(), 'latest': recent['date'].max(), 'names': recent['insider'].unique().tolist() }) except Exception as e: print(f"Error processing {ticker}: {e}") continue return sorted(clusters, key=lambda x: x['insiders'], reverse=True) Scanning the S&P 500 Running this against individual tickers is fine, but the real value is scanning broadly. I pull S&P 500 constituents from Wikipedia’s maintained list and run the detector daily: # Get S&P 500 tickers sp500 = pd.read_html( 'https://en.wikipedia.org/wiki/List_of_S%26P_500_companies' )[0]['Symbol'].tolist() # Takes about 15-20 minutes for 500 tickers # EDGAR rate limit is 10 req/sec — be respectful results = detect_insider_clusters( sp500, lookback_days=30, min_insiders=3, min_value=25000 ) for cluster in results: print(f"\n{cluster['ticker']}: {cluster['insiders']} insiders, " f"${cluster['total_value']:,.0f} total") for name in cluster['names']: print(f" - {name}") When I first ran this in January, it flagged 4 companies with 3+ insider purchases in a rolling 30-day window. Two of them outperformed the S&P over the next quarter. That’s a small sample, but it matched the academic research I mentioned earlier. Adding Slack or Telegram Alerts A detector that only runs when you remember to open a terminal isn’t very useful. I run mine on a cron job (every morning at 7 AM ET) and have it push alerts to a Telegram channel: import requests def send_telegram_alert(cluster, bot_token, chat_id): msg = ( f"🔔 Insider Cluster: ${cluster['ticker']}\n" f"Insiders buying: {cluster['insiders']}\n" f"Total value: ${cluster['total_value']:,.0f}\n" f"Window: {cluster['earliest'].strftime('%b %d')} - " f"{cluster['latest'].strftime('%b %d')}\n" f"Names: {', '.join(cluster['names'][:5])}" ) requests.post( f"https://api.telegram.org/bot{bot_token}/sendMessage", json={"chat_id": chat_id, "text": msg} ) You can also swap in Slack, Discord, or email. The detection logic stays the same — just change the notification transport. Performance Reality Check I want to be honest about what this tool can and can’t do. What works: Catching cluster buys that I’d otherwise miss entirely. Most retail investors don’t read Form 4 filings. Filtering out noise. The vast majority of insider transactions are option exercises, RSU vesting, and 10b5-1 plan sales — none of which signal much. This tool isolates the intentional purchases. Speed. EDGAR filings appear within 24-48 hours of the transaction. For cluster detection (which builds over days or weeks), that latency doesn’t matter. What doesn’t work: Single insider buys. One director buying $100K of stock might mean something, but the signal-to-noise ratio is low. Clusters are where the edge is. Short-term trading. This isn’t a day-trading signal. The academic alpha shows up over 3-6 months. Small caps with thin insider data. Some micro-caps only have 2-3 insiders total, so “cluster” detection becomes meaningless. Comparing Free Alternatives You don’t have to build your own. Here’s how the DIY approach stacks up: secform4.com — Free, decent UI, but no cluster detection. You see raw filings, not patterns. No API. Finnhub insider endpoint — Free tier includes /stock/insider-transactions, but limited to 100 transactions per call and 60 API calls/minute. Good for single-ticker lookups, not for scanning 500 tickers daily. I wrote about Finnhub and other finance APIs in my finance API comparison. OpenInsider.com — My favorite for manual browsing. Has a “cluster buys” filter built in. But no API, no automation, and the cluster definition isn’t configurable. The DIY edgartools approach wins if you want customizable filters,


---
## I Built a Regex Tester Because Regex101 Sends Your Data

- URL: https://orthogonal.info/regexlab-regex-tester/
- Date: 2026-03-31
- Category: Uncategorized
- Summary: I built a privacy-first regex tester with a multi-test-case runner because I got tired of sending production data to Regex101’s servers. Here’s how it works.

Last week I was debugging a CloudFront log parser and pasted a chunk of raw access logs into Regex101. Mid-keystroke, I realized those logs contained client IPs, user agents, and request paths from production. All of it, shipped off to someone else’s server for “processing.” That’s the moment I decided to build my own regex tester. The Problem with Existing Regex Testers 📌 TL;DR: Last week I was debugging a CloudFront log parser and pasted a chunk of raw access logs into Regex101. Mid-keystroke, I realized those logs contained client IPs, user agents, and request paths from production. All of it, shipped off to someone else’s server for “processing. 🎯 Quick Answer: A privacy-first regex tester that runs entirely in the browser with zero server communication. Unlike Regex101, no input data is transmitted or logged—ideal for testing patterns against sensitive strings like API keys or PII. I looked at three tools I’ve used for years: Regex101 is the gold standard. Pattern explanations, debugger, community library — it’s feature-rich. But it sends every keystroke to their backend. Their privacy policy says they store patterns and test strings. If you’re testing regex against production data, config files, or anything containing tokens and IPs, that’s a problem. RegExr has a solid educational angle with the animated railroad diagrams. But the interface feels like 2015, and there’s no way to test multiple strings against the same pattern without copy-pasting repeatedly. Various Chrome extensions promise offline regex testing, but they request permissions to read all your browser data. I’m not trading one privacy concern for a worse one. What none of them do: let you define a set of test cases (this string SHOULD match, this one SHOULDN’T) and run them all at once. If you write regex for input validation, URL routing, or log parsing, you need exactly that. What I Built RegexLab is a single HTML file. No build step, no npm install, no backend. Open it in a browser and it works — including offline, since it registers a service worker. Three modes: Match mode highlights every match in real-time as you type. Capture groups show up color-coded below the result. If your pattern has named groups or numbered captures, you see exactly what each group caught. Replace mode gives you a live preview of string replacement. Type your replacement pattern (with $1, $2 backreferences) and see the output update instantly. I use this constantly for log reformatting and sed-style transforms. Multi-test mode is the feature I actually wanted. Add as many test cases as you need. Mark each one as “should match” or “should not match.” Run them all against your pattern and get a pass/fail report. Green checkmark or red X, instantly. This is what makes RegexLab different from Regex101. When I’m writing a URL validation pattern, I want to throw 15 different URLs at it — valid ones, edge cases with ports and fragments, obviously invalid ones — and see them all pass or fail in one view. No scrolling, no re-running. How It Works Under the Hood The entire app is ~30KB of HTML, CSS, and JavaScript. No frameworks, no dependencies. Here’s what’s happening technically: Pattern compilation: Every keystroke triggers a debounced (80ms) recompile. The regex is compiled with new RegExp(pattern, flags) inside a try/catch. Invalid patterns show the error message directly — no cryptic “SyntaxError,” just the relevant part of the browser’s error string. Match highlighting: I use RegExp.exec() in a loop with the global flag to find every match with its index position. Then I build highlighted HTML by slicing the original string at match boundaries and wrapping matches in <span class="hl"> tags. A safety counter at 10,000 prevents infinite loops from zero-length matches (a real hazard with patterns like .*). // Simplified version of the match loop const rxCopy = new RegExp(rx.source, rx.flags); let safety = 0; while ((m = rxCopy.exec(text)) !== null && safety < 10000) { matches.push({ start: m.index, end: m.index + m[0].length }); if (m[0].length === 0) rxCopy.lastIndex++; safety++; } That lastIndex++ on zero-length matches is important. Without it, a pattern like /a*/g will match the empty string forever at the same position. Every regex tutorial skips this, and then people wonder why their browser tab freezes. Capture groups: When exec() returns an array with more than one element, elements at index 1+ are capture groups. I color-code them with four rotating colors (amber, pink, cyan, purple) and display them below the match result. Flag toggles: The flag buttons sync bidirectionally with the text input. Click a button, the text field updates. Type in the text field, the buttons update. I store flags as a simple string ("gim") and reconstruct it from button state on each click. State persistence: Everything saves to localStorage every 2 seconds — pattern, flags, test string, replacement, and all test cases. Reload the page and you’re right where you left off. The service worker caches the HTML for offline use. Common patterns library: 25 pre-built patterns for emails, URLs, IPs, dates, UUIDs, credit cards, semantic versions, and more. Click one to load it. Searchable. I pulled these from my own .bashrc aliases and validation functions I’ve written over the years. Design Decisions Dark mode by default via prefers-color-scheme. Most developers use dark themes. The light mode is there for the four people who don’t. Monospace everywhere that matters. Pattern input, test strings, results — all in SF Mono / Cascadia Code / Fira Code. Proportional fonts in regex testing are a war crime. No syntax highlighting in the pattern input. I considered it, but colored brackets and escaped characters inside an input field add complexity without much benefit. The error message and match highlighting already tell you if your pattern is right. Touch targets are 44px minimum. The flag toggle buttons, tab buttons, and action buttons all meet Apple’s HIG recommendation. I tested at 320px viewport width on my phone and everything still works. Real Use Cases Log parsing: I parse nginx access logs daily. A pattern like (\d+\.\d+\.\d+\.\d+).*?"(GET|POST)\s+([^"]+)"\s+(\d{3}) pulls IP, method, path, and status code. Multi-test mode lets me throw 10 sample log lines at it to make sure edge cases (HTTP/2 requests, URLs with quotes) don’t break it. Input validation: Building a form? Test your email/phone/date regex against a list of valid and invalid inputs in one shot. Way faster than manually testing each one. Search and replace: Reformatting dates from MM/DD/YYYY to YYYY-MM-DD? The replace mode with $3-$1-$2 backreferences shows you the result instantly. Teaching: The pattern library doubles as a learning resource. Click “Email” or “UUID” and see a production-quality regex with its flags and description. Better than Stack Overflow answers from 2012. Try It It’s live at regexlab.orthogonal.info. Works offline after the first visit. Install it as a PWA if you want it in your dock. If you want more tools like this — HashForge for hashing, JSON Forge for formatting JSON, QuickShrink for image compression — they’re all at apps.orthogonal.info. Same principle: single HTML file, zero dependencies, your data stays in your browser. Full disclosure: Mastering Regular Expressions by Jeffrey Friedl is the book that made regex click for me back in college. If you’re still guessing at lookaheads and backreferences, it’s worth the read. Regular Expressions Cookbook by Goyvaerts and Levithan is also solid if you want recipes rather than theory. And if you’re doing a lot of text processing, a good mechanical keyboard makes the difference when you’re typing backslashes all day. More Privacy-First Browser Tools JSON Forge: Privacy-First JSON Formatter HashForge: Privacy-First Hash Generator I Benchmarked 5 Image Compressors — which browser tool actually wins { "@context": "https://schema.org", "@type": "TechArticle", "headline": "I Built a Regex 


---
## Docker Compose vs Kubernetes: Secure Homelab Choices

- URL: https://orthogonal.info/docker-compose-vs-kubernetes-homelab-security/
- Date: 2026-03-31
- Category: Security
- Summary: Last year I moved my homelab from a single Docker Compose stack to a K3s cluster. It took a weekend, broke half my services, and taught me more about container security than any course I’ve taken. Here’s what I learned about when each tool actually makes sense—and the security traps in both. The rea

Last year I moved my homelab from a single Docker Compose stack to a K3s cluster. It took a weekend, broke half my services, and taught me more about container security than any course I’ve taken. Here’s what I learned about when each tool actually makes sense—and the security traps in both. The real question: how big is your homelab? 📌 TL;DR: Last year I moved my homelab from a single Docker Compose stack to a K3s cluster. It took a weekend, broke half my services, and taught me more about container security than any course I’ve taken. Here’s what I learned about when each tool actually makes sense—and the security traps in both. 🎯 Quick Answer: Use Docker Compose for homelabs with fewer than 10 containers—it’s simpler and has a smaller attack surface. Switch to K3s when you need multi-node scheduling, automatic failover, or network policies for workload isolation. I ran Docker Compose for two years. Password manager, Jellyfin, Gitea, a reverse proxy, some monitoring. Maybe 12 containers. It worked fine. The YAML was readable, docker compose up -d got everything running in seconds, and I could debug problems by reading one file. Then I hit ~25 containers across three machines. Compose started showing cracks—no built-in way to schedule across nodes, no health-based restarts that actually worked reliably, and secrets management was basically “put it in an .env file and hope nobody reads it.” That’s when I looked at Kubernetes seriously. Not because it’s trendy, but because I needed workload isolation, proper RBAC, and network policies that Docker’s bridge networking couldn’t give me. Docker Compose security: what most people miss Compose is great for getting started, but it has security defaults that will bite you. The biggest one: containers run as root by default. Most people never change this. Here’s the minimum I run on every Compose service now: version: '3.8' services: app: image: my-app:latest user: "1000:1000" read_only: true security_opt: - no-new-privileges:true cap_drop: - ALL deploy: resources: limits: memory: 512M cpus: '0.5' networks: - isolated logging: driver: json-file options: max-size: "10m" networks: isolated: driver: bridge The key additions most tutorials skip: read_only: true prevents containers from writing to their filesystem (mount specific writable paths if needed), no-new-privileges blocks privilege escalation, and cap_drop: ALL removes Linux capabilities you almost certainly don’t need. Other things I do with Compose that aren’t optional anymore: Network segmentation. Separate Docker networks for databases, frontend services, and monitoring. My Postgres container can’t talk to Traefik directly—it goes through the app layer only.Image scanning. I run Trivy on every image before deploying. One trivy image my-app:latest catches CVEs that would otherwise sit there for months.TLS everywhere. Even internal services get certificates via Let’s Encrypt and Traefik’s ACME resolver. Scan your images before they run—it takes 10 seconds and catches the obvious stuff: # Quick scan trivy image my-app:latest # Fail CI if HIGH/CRITICAL vulns found trivy image --exit-code 1 --severity HIGH,CRITICAL my-app:latest Kubernetes: when the complexity pays off I use K3s specifically because full Kubernetes is absurd for a homelab. K3s strips out the cloud-provider bloat and runs the control plane in a single binary. My cluster runs on a TrueNAS box with 32GB RAM—plenty for ~40 pods. The security features that actually matter for homelabs: RBAC — I can give my partner read-only access to monitoring dashboards without exposing cluster admin. Here’s a minimal read-only role: apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: monitoring name: dashboard-viewer rules: - apiGroups: [""] resources: ["pods", "services"] verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: viewer-binding namespace: monitoring subjects: - kind: User name: reader apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: dashboard-viewer apiGroup: rbac.authorization.k8s.io Network policies — This is the killer feature. In Compose, network isolation is coarse (whole networks). In Kubernetes, I can say “this pod can only talk to that pod on port 5432, nothing else.” If a container gets compromised, lateral movement is blocked. Namespaces — I run separate namespaces for media, security tools, monitoring, and databases. Each namespace has its own resource quotas and network policies. A runaway Jellyfin transcode can’t starve my password manager. The tradeoff is real though. I spent a full day debugging a network policy that was silently dropping traffic between my app and its database. The YAML looked right. Turned out I had a label mismatch—app: postgres vs app: postgresql. Kubernetes won’t warn you about this. It just drops packets. Networking: the part everyone gets wrong Whether you’re on Compose or Kubernetes, your reverse proxy config matters more than most security settings. I use Traefik for both setups. Here’s my Compose config for automatic TLS: version: '3.8' services: traefik: image: traefik:v3.0 command: - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--entrypoints.web.http.redirections.entryPoint.to=websecure" - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true" - "[email protected]" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" volumes: - "./letsencrypt:/letsencrypt" ports: - "80:80" - "443:443" Key detail: that HTTP-to-HTTPS redirect on the web entrypoint. Without it, you’ll have services accessible over plain HTTP and not realize it until someone sniffs your traffic. For storage, encrypt volumes at rest. If you’re on ZFS (like my TrueNAS setup), native encryption handles this. For Docker volumes specifically: # Create a volume backed by encrypted storage docker volume create --driver local \ --opt type=none \ --opt o=bind \ --opt device=/mnt/encrypted/app-data \ my_secure_volume My Homelab Security Hardening Checklist After running both Docker Compose and K3s in production for over a year, I’ve distilled my security hardening into a checklist I apply to every new service. The specifics differ between the two platforms, but the principles are the same: minimize attack surface, enforce least privilege, and assume every container will eventually be compromised. Docker Compose hardening — here’s my battle-tested template with every security flag I use. This goes beyond the basics I showed earlier: version: '3.8' services: secure-app: image: my-app:latest user: "1000:1000" read_only: true security_opt: - no-new-privileges:true - seccomp:seccomp-profile.json cap_drop: - ALL cap_add: - NET_BIND_SERVICE # Only if binding to ports below 1024 tmpfs: - /tmp:size=64M,noexec,nosuid - /run:size=32M,noexec,nosuid deploy: resources: limits: memory: 512M cpus: '0.5' reservations: memory: 128M cpus: '0.1' healthcheck: test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/health"] interval: 30s timeout: 5s retries: 3 start_period: 10s restart: unless-stopped networks: - app-tier volumes: - app-data:/data # Only specific paths are writable logging: driver: json-file options: max-size: "10m" max-file: "3" volumes: app-data: driver: local networks: app-tier: driver: bridge internal: true # No direct internet access The key additions here: seccomp:seccomp-profile.json loads a custom seccomp profile that restricts which syscalls the container can make. The default Docker seccomp profile blocks about 44 syscalls, but you can tighten it further for specific workloads. The tmpfs mounts with noexec prevent anything written to temp directories from being executed—this blocks a whole class of container escape techniques. And internal: true on the network means the container can only reach other containers on the same network, not the internet directly. K3s hardening — Kubernetes gives you


---
## Best Drives for TrueNAS 2026: HDDs, SSDs & My Setup

- URL: https://orthogonal.info/best-drives-for-truenas-in-2026-hdds-ssds-and-what-i-actually-run/
- Date: 2026-03-31
- Category: Homelab
- Summary: A practical guide to picking the right HDDs and SSDs for TrueNAS. CMR vs SMR, SLOG and L2ARC explained, plus what I actually run in my homelab.

Last month I lost a drive in my TrueNAS mirror. WD Red, three years old, SMART warnings I’d been ignoring for two weeks. The rebuild took 14 hours on spinning rust, and the whole time I was thinking: if the second drive goes, that’s 8TB of media and backups gone. That rebuild forced me to actually research what I was putting in my NAS instead of just grabbing whatever was on sale. Turns out, picking the right drives for ZFS matters more than most people realize — and the wrong choice can cost you data or performance. Here’s what I learned, what I’m running now, and what I’d buy if I were building from scratch today. CMR vs. SMR: This Actually Matters for ZFS 📌 TL;DR: Last month I lost a drive in my TrueNAS mirror. WD Red, three years old, SMART warnings I’d been ignoring for two weeks. The rebuild took 14 hours on spinning rust, and the whole time I was thinking: if the second drive goes, that’s 8TB of media and backups gone. 🎯 Quick Answer: For TrueNAS in 2026, use CMR HDDs (not SMR) for bulk storage—Seagate Exos X20 or WD Ultrastar are top picks. Add a mirrored SSD SLOG for sync writes and an L2ARC SSD for read caching on frequently accessed datasets. Before anything else — check if your drive uses CMR (Conventional Magnetic Recording) or SMR (Shingled Magnetic Recording). ZFS and SMR don’t get along. SMR drives use overlapping write tracks to squeeze in more capacity, which means random writes are painfully slow. During a resilver (ZFS’s version of a rebuild), an SMR drive can take 3-4x longer than CMR. WD got caught shipping SMR drives labeled as NAS drives back in 2020 (the WD Red debacle). They’ve since split the line: WD Red Plus = CMR, plain WD Red = SMR. Don’t buy the plain WD Red for a NAS. I made this mistake once. Never again. Seagate’s IronWolf line is all CMR. Toshiba N300 — also CMR. If you’re looking at used enterprise drives (which I’ll get to), they’re all CMR. The Drives I’d Actually Buy Today For Bulk Storage: WD Red Plus 8TB The WD Red Plus 8TB (WD80EFPX) is what I’m running right now. 5640 RPM, CMR, 256MB cache. It’s not the fastest drive, but it runs cool and quiet — important when your NAS sits in a closet six feet from your bedroom. Price per TB on the 8TB sits around $15-17 at time of writing. The sweet spot for capacity vs. cost. Going to 12TB or 16TB drops the per-TB price slightly, but the failure risk per drive goes up — a single 16TB drive failing is a lot more data at risk during rebuild than an 8TB. I run these in a mirror (RAID1 equivalent in ZFS). Two drives, same data on both. Simple, reliable, and rebuild time is reasonable. Full disclosure: affiliate link. If You Prefer Seagate: IronWolf 8TB The Seagate IronWolf 8TB (ST8000VN004) is the other solid choice. 7200 RPM, CMR, 256MB cache. Faster spindle speed means slightly better sequential performance, but also more heat and noise. Seagate includes their IronWolf Health Management software, which hooks into most NAS operating systems including TrueNAS. It gives you better drive health telemetry than standard SMART. Whether that’s worth the slightly higher price depends on how paranoid you are about early failure detection. (I’m very paranoid, but I still went WD — old habits.) Both drives have a 3-year warranty. The IronWolf Pro bumps that to 5 years and adds rotational vibration sensors (matters more in 8+ bay enclosures). For a 4-bay homelab NAS, the standard IronWolf is enough. Full disclosure: affiliate link. Budget Option: Used Enterprise Drives Here’s my hot take: refurbished enterprise drives are underrated for homelabs. An HGST Ultrastar HC320 8TB can be found for $60-80 on eBay — roughly half the price of new consumer NAS drives. These were built for 24/7 operation in data centers. They’re louder (full 7200 RPM, no acoustic management), but they’re tanks. The catch: no warranty, unknown hours, and you’re gambling on remaining lifespan. I run one pool with used enterprise drives and another with new WD Reds. The enterprise drives have been fine for two years. But I also keep backups, because I’m not an idiot. SSDs in TrueNAS: SLOG, L2ARC, and When They’re Worth It ZFS has two SSD acceleration features that confuse a lot of people: SLOG (write cache) and L2ARC (read cache). Let me save you some research time. SLOG (Separate Log Device) SLOG moves the ZFS Intent Log to a dedicated SSD. This only helps if you’re doing a lot of synchronous writes — think iSCSI targets, NFS with sync enabled, or databases. If you’re mostly streaming media and storing backups, SLOG does nothing for you. If you DO need a SLOG, the drive needs high write endurance and a power-loss protection capacitor. The Intel Optane P1600X 118GB is the gold standard here — extremely low latency and designed for exactly this workload. They’re getting harder to find since Intel killed the Optane line, but they pop up on Amazon periodically. Full disclosure: affiliate link. Don’t use a consumer NVMe SSD as a SLOG. If it loses power mid-write without a capacitor, you can lose the entire transaction log. That’s your data. L2ARC (Level 2 Adaptive Replacement Cache) L2ARC is a read cache on SSD that extends your ARC (which lives in RAM). The thing most guides don’t tell you: L2ARC uses about 50-70 bytes of RAM per cached block to maintain its index. So adding a 1TB L2ARC SSD might eat 5-10GB of RAM just for the metadata. Rule of thumb: if you have less than 64GB of RAM, L2ARC probably hurts more than it helps. Your RAM IS your cache in ZFS — spend money on more RAM before adding an L2ARC SSD. I learned this the hard way on a 32GB system where L2ARC actually slowed things down. If you do have the RAM headroom and want L2ARC, any decent NVMe drive works. I’d grab a Samsung 990 EVO 1TB — good endurance, solid random read performance, and the price has come down a lot. Full disclosure: affiliate link. What My Actual Setup Looks Like For context, I run TrueNAS Scale on an older Xeon workstation with 64GB ECC RAM. Here’s the drive layout: Pool: tank (main storage) Mirror: 2x WD Red Plus 8TB (WD80EFPX) Pool: fast (VMs and containers) Mirror: 2x Samsung 870 EVO 1TB SATA SSD No SLOG (my workloads are async) No L2ARC (64GB RAM handles my working set) Total usable: ~8TB spinning + ~1TB SSD. The SSD pool runs my Docker containers and any VM images. Everything else — media, backups, time machine targets — lives on the spinning rust pool. This separation matters. ZFS performs best when pools have consistent drive types. Mixing SSDs and HDDs in the same vdev is asking for trouble (the pool performs at the speed of the slowest drive). ECC RAM: Not Optional, Fight Me While we’re talking about TrueNAS hardware — get ECC RAM. Yes, TrueNAS will run without it. No, that doesn’t mean you should. ZFS checksums every block, which means it can detect corruption. But if your RAM flips a bit (which non-ECC RAM does more often than you’d think), ZFS might write that corrupted data to disk AND update the checksum to match. Now you have silent data corruption that ZFS thinks is fine. With ECC, the memory controller catches and corrects single-bit errors before they hit disk. Used DDR4 ECC UDIMMs are cheap. A 32GB kit runs $40-60 on eBay. There’s no excuse not to use it if your board supports it. If you’re building a new system, look at Xeon E-series or AMD platforms that support ECC. How to Check What You Already Have Already running TrueNAS? Here’s how to check your drive health before something fails: # List all drives with model and serial smartctl --scan | while read dev rest; do echo "=== $dev ===" smartctl -i "$dev" | grep -E "Model|Serial|Capacity" smartctl -A "$dev" | grep -E "Reallocated|Current_Pending|Power_On" done # Quick ZFS pool health check zpool status -v Watch for Reallocated_Sector_Ct above zero and Current_Pending_Sector above zero. Those are your early warning signs. If both are climbing, start shopping for a replacement drive now — don’t wait for the failure like I did. The Short Version If you’re building a 


---
## Claude Code Leak: npm Security, TypeScript, AI Architecture

- URL: https://orthogonal.info/claude-code-leak-lessons-in-npm-security-typescript-analysis-and-ai-tool-architecture/
- Date: 2026-03-31
- Category: Deep Dives
- Summary: When source maps for a major AI coding tool leaked via an npm package, I spent a week analyzing what was exposed and how it happened. The leak revealed internal architecture, agent orchestration patterns, and TypeScript code that was never meant to be public. This isn’t theoretical — it’s a case stu

When source maps for a major AI coding tool leaked via an npm package, I spent a week analyzing what was exposed and how it happened. The leak revealed internal architecture, agent orchestration patterns, and TypeScript code that was never meant to be public. This isn’t theoretical — it’s a case study in what happens when your build pipeline doesn’t strip debug artifacts before publishing. This post breaks down the specific npm security failures that led to the leak, what the exposed source maps revealed about AI tool architecture, and the exact pipeline configuration that prevents this from happening to your packages. Introduction: Why npm Security Matters 📌 TL;DR: A leaked npm package exposed source maps revealing internal AI tool architecture. Prevention: use the files field in package.json (whitelist, not blacklist), run npm pack --dry-run in CI to verify contents, strip source maps in production builds with sourceMap: false in tsconfig, and scan with npm-packlist before every publish. 🎯 Quick Answer: Leaked Claude Code npm source maps reveal a TypeScript architecture using tool-use loops, diff-based file editing, and conversation-context management. The leak highlights why publishing source maps in production npm packages is a security risk. Alright, let’s talk about the elephant in the server room: npm security. If you haven’t heard, there have been numerous incidents where sensitive files or configurations found their way into the wild due to improper packaging or publishing practices. Think of it as accidentally leaving your TypeScript codebase out in the rain—except the rain is the entire internet, and your code is now everyone’s business. For software engineers and DevSecOps pros like us, this is a wake-up call to rethink how we handle security, especially when it comes to npm packages and build artifacts. Here’s the thing: npm packages are the backbone of modern development. They’re great for sharing reusable code, managing dependencies, and speeding up development workflows. But when security lapses occur—like improperly secured source maps or sensitive files making their way into public repositories—it’s like leaving your house keys under the doormat. Sure, it’s convenient, but it’s also an open invitation to trouble. In this article, we’ll dive into the implications of npm security, why protecting source maps is more critical than ever, and how to safeguard your projects from similar mishaps. From analyzing your TypeScript codebase for vulnerabilities to ensuring your source maps don’t spill the beans, we’ve got you covered. And yes, there will be a few jokes along the way—because if we can’t laugh at our mistakes, what’s the point? 💡 Pro Tip: Always double-check your .gitignore file. It’s like a bouncer for your repo—don’t let sensitive files sneak past it. Understanding the Security Implications of Leaked Source Maps Ah, source maps. They’re like the treasure maps of your TypeScript codebase—except instead of leading to gold doubloons, they lead to your beautifully crafted code. If you’ve ever debugged a web app, you’ve probably thanked the heavens for source maps. They translate your minified JavaScript back into something readable, making debugging less of a soul-crushing experience. But here’s the catch: when these maps are leaked, they can expose sensitive information faster than I expose my lack of gym attendance. Let’s break it down. Source maps are files that link your compiled JavaScript code back to the original TypeScript (or whatever language you’re using). They’re a lifesaver for developers, but they’re also a potential goldmine for attackers. Why? Because they reveal your code structure, variable names, and sometimes even comments—basically everything you’d rather keep private. If you’re shipping npm packages, you need to care about npm source maps security. Otherwise, you might accidentally hand over your app’s blueprint to the bad guys. So, what’s the real-world risk here? Attackers can use leaked source maps to reverse-engineer your app, identify vulnerabilities, and exploit them. They can even uncover hidden secrets like API keys or proprietary algorithms. If you’re using AI coding tools, you might be tempted to rely on them for code analysis, but remember: even AI can’t save you from bad security practices. 💡 Specific config: In tsconfig.json, set "sourceMap": false and "declarationMap": false for production builds. In Webpack: devtool: false (not 'source-map' or 'hidden-source-map'). In Rollup: omit the sourcemap output option. Then verify: run find dist/ -name '*.map' — if any files appear, your build config is wrong. In conclusion, leaked source maps are like leaving the back door open for hackers. They’re a small detail that can have massive consequences. If you’re working with npm packages or AI coding tools, take the time to review your security practices. Trust me, you don’t want to be the next headline in the “DevSecOps Horror Stories” newsletter. Analyzing a TypeScript Codebase: Architectural Insights Ah, large-scale TypeScript codebases. They’re like opening a treasure chest, except instead of gold coins, you find a labyrinth of TypeScript files, multi-agent orchestration patterns, and enough modular design to make a LEGO engineer weep with envy. Let’s dive into this architectural marvel (or monstrosity, depending on your caffeine levels) and see what makes it tick—and why analyzing it is like trying to untangle a pair of headphones you left in your pocket for a year. First off, let’s talk structure. A well-designed TypeScript codebase is often divided into neatly organized modules, each with its own specific role, but they all come together in a way that feels like a symphony—if the conductor were a robot trying to learn Beethoven on the fly. One of the standout architectural patterns here is multi-agent orchestration. Think of it as a group of highly specialized agents (or microservices) working together to accomplish tasks. Each agent knows its job, communicates with others through well-defined APIs, and occasionally throws an error just to remind you it’s still a machine. This pattern is great for scalability and fault isolation, but it can also make debugging feel like herding cats—except the cats are on different continents and speak different programming languages. Another key feature is the codebase’s modular design. Modules are like LEGO bricks: self-contained, reusable, and capable of building something amazing when combined. But here’s the catch—just like LEGO, if you step on the wrong module (read: introduce a breaking change), it’s going to hurt. A lot. The modularity is a double-edged sword: it keeps the codebase maintainable but also means you need a map, a flashlight, and probably a snack to navigate it effectively. 💡 Pro Tip: When analyzing large-scale TypeScript projects, always start with the package.json. It’s the Rosetta Stone of dependencies and can save you hours of head-scratching. Now, let’s address the elephant in the room: analyzing large-scale TypeScript projects is hard. Between the sheer volume of files, the intricate dependency graphs, and the occasional “What were they thinking?” moments, it’s easy to feel overwhelmed. And don’t get me started on npm source maps security. If you’re not careful, those source maps can leak sensitive information faster than you can say “security breach.” Here’s a quick example of what you might encounter in a codebase like this: // A simple example of a modular agent in TypeScript export class DataProcessor { private data: string[]; constructor(data: string[]) { this.data = data; } public process(): string[] { return this.data.map(item => item.toUpperCase()); } } // Usage const processor = new DataProcessor(['typescript', 'codebase', 'analysis']); console.log(processor.process()); // Output: ['TYPESCRIPT', 'CODEBASE', 'ANALYSIS'] Looks straightforward, right? Now imagine this times a thousand, with interdependencies, async calls, and a sprinkl


---
## Self-Host Ollama: Local LLM Inference on Your Homelab

- URL: https://orthogonal.info/self-host-ollama-on-your-homelab-local-llm-inference-without-the-cloud-bill/
- Date: 2026-03-31
- Category: Homelab
- Summary: Run Ollama on your homelab for free local LLM inference. Real benchmarks, hardware recs, and setup guide from Mac Mini to RTX 3090.

The $300/Month Problem 📌 TL;DR: The $300/Month Problem I hit my OpenAI API billing dashboard last month and stared at $312.47. That’s what three months of prototyping a RAG pipeline cost me — and most of those tokens were wasted on testing prompts that didn’t work. 🎯 Quick Answer: Self-hosting Ollama on a homelab with a used GPU can save over $300/month compared to OpenAI API costs. Run models like Llama 3 and Mistral locally with full data privacy and no per-token fees. I hit my OpenAI API billing dashboard last month and stared at $312.47. That’s what three months of prototyping a RAG pipeline cost me — and most of those tokens were wasted on testing prompts that didn’t work. Meanwhile, my TrueNAS box sat in the closet pulling 85 watts, running Docker containers I hadn’t touched in weeks. That’s when I started looking at Ollama — a dead-simple way to run open-source LLMs locally. No API keys, no rate limits, no surprise invoices. Three weeks in, I’ve moved about 80% of my development-time inference off the cloud. Here’s exactly how I set it up, what hardware actually matters, and the real performance numbers nobody talks about. Why Ollama Over vLLM, LocalAI, or text-generation-webui I tried all four. Here’s why I stuck with Ollama: vLLM is built for production throughput — batched inference, PagedAttention, the works. It’s also a pain to configure if you just want to ask a model a question. Setup took me 45 minutes and required building from source to get GPU support working on my machine. LocalAI supports more model formats (GGUF, GPTQ, AWQ) and has an OpenAI-compatible API out of the box. But the documentation is scattered, and I hit three different bugs in the Whisper integration before giving up. text-generation-webui (oobabooga) is great if you want a chat UI. But I needed an API endpoint I could hit from scripts and other services, and the API felt bolted on. Ollama won because: one binary, one command to pull a model, instant OpenAI-compatible API on port 11434. I had Llama 3.1 8B answering prompts in under 2 minutes from a cold start. That matters when you’re trying to build things, not babysit infrastructure. Hardware: What Actually Moves the Needle I’m running Ollama on a Mac Mini M2 with 16GB unified memory. Here’s what I learned about hardware that actually affects performance: Memory is everything. LLMs need to fit entirely in RAM (or VRAM) to run at usable speeds. A 7B parameter model in Q4_K_M quantization needs about 4.5GB. A 13B model needs ~8GB. A 70B model needs ~40GB. If the model doesn’t fit, it pages to disk and you’re looking at 0.5 tokens/second — basically unusable. GPU matters less than you think for models under 13B. Apple Silicon’s unified memory architecture means the M1/M2/M3 chips run these models surprisingly well — I get 35-42 tokens/second on Llama 3.1 8B with my M2. A dedicated NVIDIA GPU is faster (an RTX 3090 with 24GB VRAM will push 70+ tok/s on the same model), but the Mac Mini uses 15 watts doing it versus 350+ watts for the 3090. CPU-only is viable for small models. On a 4-core Intel box with 32GB RAM, I was getting 8-12 tokens/second on 7B models. Not great for chat, but perfectly fine for batch processing, embeddings, or code review pipelines where latency doesn’t matter. If you’re building a homelab inference box from scratch, here’s what I’d buy today: Budget ($400-600): A used Mac Mini M2 with 16GB RAM runs 7B-13B models at very usable speeds. Power draw is laughable — 15-25 watts under inference load. Mid-range ($800-1200): A Mac Mini M4 with 32GB lets you run 30B models and keeps two smaller models hot in memory. The M4 with 32GB unified memory is the sweet spot for most homelab setups. GPU path ($500-900): If you already have a Linux box, grab a used RTX 3090 24GB — they’ve dropped to $600-800 and the 24GB VRAM handles 13B models at 70+ tok/s. Just make sure your PSU can handle the 350W draw. The Setup: 5 Minutes, Not Kidding On macOS or Linux: curl -fsSL https://ollama.com/install.sh | sh ollama serve & ollama pull llama3.1:8b That’s it. The model downloads (~4.7GB for the Q4_K_M quantized 8B), and you’ve got an API running on localhost:11434. Test it: curl http://localhost:11434/api/generate -d '{ "model": "llama3.1:8b", "prompt": "Explain TCP three-way handshake in two sentences.", "stream": false }' For Docker (which is what I use on TrueNAS): docker run -d \ --name ollama \ -v ollama_data:/root/.ollama \ -p 11434:11434 \ --restart unless-stopped \ ollama/ollama:latest Then pull your model into the running container: docker exec ollama ollama pull llama3.1:8b Real Benchmarks: What I Actually Measured I ran each model 10 times with the same prompt (“Write a Python function to merge two sorted lists with O(n) complexity, with docstring and type hints”) and averaged the results. Mac Mini M2, 16GB, nothing else running: Model Size (Q4_K_M) Tokens/sec Time to first token RAM used Llama 3.1 8B 4.7GB 38.2 0.4s 5.1GB Mistral 7B v0.3 4.1GB 41.7 0.3s 4.6GB CodeLlama 13B 7.4GB 22.1 0.8s 8.2GB Llama 3.1 70B (Q2_K) 26GB 3.8 4.2s 28GB* *The 70B model technically ran on 16GB with aggressive quantization but spent half its time swapping. I wouldn’t recommend it without 32GB+ RAM. For context: GPT-4o through the API typically returns 50-80 tokens/second, but you’re paying per token and dealing with rate limits. 38 tokens/second from a local 8B model is fast enough that you barely notice the difference when coding. Making It Useful: The OpenAI-Compatible API This is the part that made Ollama actually practical for me. It exposes an OpenAI-compatible endpoint at /v1/chat/completions, which means you can point any tool that uses the OpenAI SDK at your local instance by just changing the base URL: from openai import OpenAI client = OpenAI( base_url="http://192.168.0.43:11434/v1", api_key="not-needed" # Ollama doesn't require auth ) response = client.chat.completions.create( model="llama3.1:8b", messages=[{"role": "user", "content": "Review this PR diff..."}] ) print(response.choices[0].message.content) I use this for: Automated code review — a git hook sends diffs to the local model before I push Log analysis — pipe structured logs through a prompt that flags anomalies Documentation generation — point it at a module and get decent first-draft docstrings Embedding generation — ollama pull nomic-embed-text gives you a solid embedding model for RAG without paying per-token None of these need GPT-4 quality. A well-prompted 8B model handles them at 90%+ accuracy, and the cost is literally zero per request. Gotchas I Hit (So You Don’t Have To) Memory pressure kills everything. When Ollama loads a model, it stays in memory until another model evicts it or you restart the service. If you’re running other containers on the same box, set OLLAMA_MAX_LOADED_MODELS=1 to prevent two 8GB models from eating all your RAM and triggering the OOM killer. Network binding matters. By default Ollama only listens on 127.0.0.1:11434. If you want other machines on your LAN to use it (which is the whole point of a homelab setup), set OLLAMA_HOST=0.0.0.0. But don’t expose this to the internet — there’s no auth layer. Put it behind a reverse proxy with basic auth or Tailscale if you need remote access. Quantization matters more than model size. A 13B model at Q4_K_M often beats a 7B at Q8. The sweet spot for most use cases is Q4_K_M — it’s roughly 4 bits per weight, which keeps quality surprisingly close to full precision while cutting memory by 4x. Context length eats memory fast. The default context window is 2048 tokens. Bumping it to 8192 with ollama run llama3.1 --ctx-size 8192 roughly doubles memory usage. Plan accordingly. When to Stay on the Cloud I still use GPT-4o and Claude for anything requiring deep reasoning, long context, or multi-step planning. Local 8B models are not good at complex architectural analysis or debugging subtle race conditions. They’re excellent at well-scoped, repetitive tasks with clear instructions. The split 


---
## Securing GitHub Actions: OIDC, Least Privilege, & More

- URL: https://orthogonal.info/how-to-secure-github-actions-oidc-authentication-least-privilege-and-supply-chain-attack-prevention/
- Date: 2026-03-30
- Category: Deep Dives
- Summary: Did you know that 84% of developers using GitHub Actions admit they’re unsure if their workflows are secure? That’s like building a fortress but forgetting to lock the front gate. And with supply chain attacks on the rise, every misstep could be the one that lets attackers waltz right into your CI/C

Did you know that 84% of developers using GitHub Actions admit they’re unsure if their workflows are secure? That’s like building a fortress but forgetting to lock the front gate. And with supply chain attacks on the rise, every misstep could be the one that lets attackers waltz right into your CI/CD pipeline. If you’ve ever stared at your GitHub Actions configuration wondering if you’re doing enough to keep bad actors out—or worse, if you’ve accidentally left the keys under the mat—this article is for you. We’re diving into OIDC authentication, least privilege principles, and how to fortify your workflows against supply chain attacks. By the end, you’ll be armed with practical tips to harden your pipelines without losing your sanity (or your deployment logs). Let’s get secure, one action at a time! GitHub Actions Security Challenges 📌 TL;DR: Did you know that 84% of developers using GitHub Actions admit they’re unsure if their workflows are secure? That’s like building a fortress but forgetting to lock the front gate. 🎯 Quick Answer: 84% of developers are unsure if their GitHub Actions workflows are secure. Replace long-lived secrets with OIDC federation, pin actions to commit SHAs instead of tags, and apply least-privilege permissions with explicit permissions: blocks. If you’ve ever set up a CI/CD pipeline with GitHub Actions, you know it’s like discovering a magical toolbox that automates everything from testing to deployment. It’s fast, powerful, and makes you feel like a wizard—until you realize that with great power comes great responsibility. And by responsibility, I mean security challenges that can make you question every life choice leading up to this moment. GitHub Actions is a fantastic tool for developers and DevOps teams, but it’s also a juicy target for attackers. Why? Because it’s deeply integrated into your repositories and workflows, making it a potential goldmine for anyone looking to exploit your code or infrastructure. Let’s talk about some of the common security challenges you might face while using GitHub Actions. OIDC authentication: OpenID Connect (OIDC) is a big improvement for securely accessing cloud resources without hardcoding secrets. But if you don’t configure it properly, you might as well leave your front door open with a “Free Wi-Fi” sign. Least privilege permissions: Giving your workflows more permissions than they need is like handing your toddler a chainsaw—sure, it might work out, but the odds aren’t in your favor. Always aim for the principle of least privilege. Supply chain attacks: Your dependencies are like roommates—you trust them until you find out they’ve been stealing your snacks (or, in this case, your secrets). Be vigilant about what third-party actions you’re using. For a deeper dive, see our guide to securing supply chains with SBOM & Sigstore. Ignoring these challenges is like ignoring a check engine light—it might not seem like a big deal now, but it’s only a matter of time before something explodes. Addressing these issues proactively can save you a lot of headaches (and possibly your job). 💡 Pro Tip: Always review the permissions your workflows request and use OIDC tokens to eliminate the need for long-lived secrets. Your future self will thank you. In the next sections, we’ll dive deeper into these challenges and explore practical ways to secure your GitHub Actions workflows. Spoiler alert: it’s not as scary as it sounds—promise! Understanding OIDC Authentication in GitHub Actions OIDC eliminates the single biggest risk in GitHub Actions: stored cloud credentials. Instead of keeping long-lived access keys as repository secrets (which any workflow step can read and exfiltrate), OIDC generates a short-lived token scoped to the specific workflow run. The token expires in minutes, is tied to the repository and branch, and can’t be reused. I’ve migrated every CI/CD pipeline I manage from stored credentials to OIDC. The setup takes 30 minutes per cloud provider, and the security improvement is massive — you go from “any compromised action can steal permanent cloud access” to “tokens expire before an attacker can use them.” How OIDC Works in GitHub Actions Here’s the 10,000-foot view: when your GitHub Actions workflow needs to access a cloud service (like AWS or Azure), it uses OIDC to request a token. This token is verified by the cloud provider, and if everything checks out, access is granted. The best part? The token is short-lived, so even if it gets compromised, it’s useless after a short period. Here’s a quick example of how you might configure OIDC for AWS in your GitHub Actions workflow: # .github/workflows/deploy.yml name: Deploy to AWS on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write # Required for OIDC contents: read steps: - name: Checkout code uses: actions/checkout@v3 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: role-to-assume: arn:aws:iam::123456789012:role/MyOIDCRole aws-region: us-east-1 - name: Deploy application run: ./deploy.sh Notice the id-token: write permission? That’s the secret sauce enabling OIDC. It lets GitHub Actions request a token from its OIDC provider, which AWS then validates before granting access. Why OIDC Beats Traditional Secrets Using OIDC over traditional secrets-based authentication is like upgrading from a rusty bike to a Tesla. Here’s why: Improved security: No more storing long-lived credentials in your repo. Tokens are short-lived and scoped to specific actions. Least privilege permissions: You can fine-tune access, ensuring workflows only get the permissions they need. Reduced maintenance: Forget about rotating secrets or worrying if someone forgot to update them. OIDC handles it all dynamically. 💡 Pro Tip: Always review your workflow’s permissions. Grant only what’s necessary to follow the principle of least privilege. How OIDC Improves Security Let’s be real—long-lived credentials are a ticking time bomb. They’re like leaving your house key under the doormat: convenient but risky. OIDC eliminates this risk by issuing tokens that expire quickly and are tied to specific workflows. Even if someone intercepts the token, it’s practically useless outside its intended scope. In conclusion, OIDC authentication in GitHub Actions is a win-win for security and simplicity. It’s like having a personal assistant who handles all the boring, error-prone credential management for you. So, ditch those long-lived secrets and embrace the future of CI/CD security. Your DevOps team will thank you! Implementing Least Privilege Permissions in Workflows Let’s talk about the principle of least privilege. It’s like giving your cat access to just the litter box and not the entire pantry. Sure, your cat might be curious about the pantry, but trust me, you don’t want to deal with the chaos that follows. Similarly, in the world of CI/CD, granting excessive permissions in your workflows is an open invitation for trouble. And by trouble, I mean security vulnerabilities that could make your DevOps pipeline the talk of the hacker community. When it comes to GitHub Actions security, the principle of least privilege—a cornerstone of zero trust architecture—ensures that your workflows only have access to what they absolutely need to get the job done—nothing more, nothing less. Let’s dive into how you can configure this and avoid common pitfalls. Steps to Configure Least Privilege Permissions for GitHub Actions Start with a deny-all approach: By default, set permissions to read or none for everything. You can do this in your workflow file under the permissions key. Grant specific permissions: Only enable the permissions your workflow needs. For example, if your workflow needs to push to a repository, grant write access to contents. Use OIDC authentication: OpenID Connect (OIDC) allows your workflows to authenticate with cloud providers securely without hardcoding secrets. This is a big improvement for red


---
## Terraform Security: Encryption, IAM & Drift Detection

- URL: https://orthogonal.info/terraform-security-best-practices-encryption-iam-and-drift-detection/
- Date: 2026-03-30
- Category: Deep Dives
- Summary: What happens when your Terraform state file ends up in the wrong hands? Spoiler: it’s not pretty, and your cloud environment might as well send out party invitations to every hacker on the internet. Keeping your Terraform setup secure can feel like trying to lock the front door while someone’s alrea

What happens when your Terraform state file ends up in the wrong hands? Spoiler: it’s not pretty, and your cloud environment might as well send out party invitations to every hacker on the internet. Keeping your Terraform setup secure can feel like trying to lock the front door while someone’s already sneaking in through the window. But don’t worry—this article will help you safeguard your state files with encryption, configure IAM policies that won’t break your workflows (or your spirit), and detect drift before it turns into a full-blown disaster. Let’s dive in, and maybe even have a laugh along the way—because crying over misconfigured permissions is so last year. Introduction: Why Terraform Security Matters 📌 TL;DR: What happens when your Terraform state file ends up in the wrong hands? Spoiler: it’s not pretty, and your cloud environment might as well send out party invitations to every hacker on the internet. 🎯 Quick Answer: Secure Terraform state by enabling S3 backend encryption with a KMS key, restricting state access via IAM policies, enabling DynamoDB state locking, and running terraform plan drift detection in CI to catch unauthorized infrastructure changes. Let’s face it: Terraform is like the Swiss Army knife of infrastructure as code (IaC). It’s powerful, versatile, and can make you feel like a wizard conjuring up entire cloud environments with a few lines of HCL. But with great power comes great responsibility—or, in this case, great security risks. If you’re not careful, your Terraform setup can go from “cloud hero” to “security zero” faster than you can say terraform apply. Cloud engineers and DevOps teams often face a minefield of security challenges when using Terraform. From accidentally exposing sensitive data in state files to over-permissioned IAM roles that scream “hack me,” the risks are real. And don’t even get me started on the chaos of managing shared state files in a team environment. It’s like trying to share a single toothbrush—gross and a bad idea. So, why does securing Terraform matter so much in production? Because your infrastructure isn’t just a playground; it’s the backbone of your business. A poorly secured Terraform setup can lead to data breaches, compliance violations, and sleepless nights filled with regret. Trust me, I’ve been there—it’s not fun. 💡 Pro Tip: Always encrypt your state files and follow Terraform security best practices, like using least privilege IAM roles. Your future self will thank you. In this blog, we’ll dive into practical tips and strategies to keep your Terraform setup secure and your cloud environments safe. Let’s get started before the hackers do! Securing Terraform State Files with Encryption Let’s talk about Terraform state files. These little critters are like the diary of your infrastructure—holding all the juicy details about your resources, configurations, and even some sensitive data. If someone gets unauthorized access to your state file, it’s like handing them the keys to your cloud kingdom. Not ideal, right? Now, before you panic and start imagining hackers in hoodies sipping coffee while reading your state file, let’s discuss how to protect it. The answer? Encryption. Think of it as putting your state file in a vault with a combination lock. Even if someone gets their hands on it, they can’t read it without the secret code. Why Terraform State Files Are Critical and Sensitive Terraform state files are the source of truth for your infrastructure. They track the current state of your resources, which Terraform uses to determine what needs to be added, updated, or deleted. Unfortunately, these files can also contain sensitive data like resource IDs, secrets, and even passwords (yes, passwords—yikes!). If exposed, this information can lead to unauthorized access or worse, a full-blown data breach. Best Practices for Encrypting State Files Encrypting your state files is not just a good idea; it’s a must-do for anyone running Terraform in production. Here are some best practices: Use backend storage with built-in encryption: AWS S3 with KMS (Key Management Service) or Azure Blob Storage with encryption are excellent choices. These services handle encryption for you, so you don’t have to reinvent the wheel. Enable least privilege IAM: Ensure that only authorized users and systems can access your state file. Use IAM policies to restrict access and regularly audit permissions. Version your state files: Store previous versions of your state file securely so you can recover from accidental changes or corruption. 💡 Pro Tip: Always enable server-side encryption when using cloud storage for your state files. It’s like locking your front door—basic but essential. Real-World Example: How Encryption Prevented a Data Breach A friend of mine (who shall remain nameless to protect their dignity) once accidentally exposed their Terraform state file on a public S3 bucket. Cue the horror music. Fortunately, they had enabled KMS encryption on the bucket. Even though the file was publicly accessible for a brief moment, the encryption ensured that no one could read its contents. Crisis averted, lesson learned: encryption is your best friend. Code Example: Setting Up AWS S3 Backend with KMS Encryption terraform { backend "s3" { bucket = "my-terraform-state-bucket" key = "terraform/state/production.tfstate" region = "us-east-1" kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/abc123" } } In this example, we’re using an S3 bucket with KMS encryption enabled. The kms_key_id parameter specifies the KMS key used for encryption. Server-side encryption is automatically handled by S3 when configured correctly. Simple, effective, and hacker-proof (well, almost). So, there you have it—encrypt your Terraform state files like your infrastructure depends on it. Because, spoiler alert: it does. Implementing Least Privilege IAM Policies for Terraform Least privilege is just as critical in CI/CD—see how it applies to securing GitHub Actions workflows. If you’ve ever handed out overly permissive IAM roles in your Terraform setup, you know the feeling—it’s like giving your dog the keys to your car and hoping for the best. Sure, nothing might go wrong, but when it does, it’s going to be spectacularly messy. That’s why today we’re diving into the principle of least privilege and how to apply it to your Terraform workflows without losing your sanity (or your state file). The principle of least privilege is simple: give your Terraform processes only the permissions they absolutely need and nothing more. Think of it like packing for a weekend trip—you don’t need to bring your entire wardrobe, just the essentials. This approach reduces the risk of privilege escalation, accidental deletions, or someone (or something) running off with your cloud resources. 💡 Pro Tip: Always encrypt your Terraform state file. It’s like locking your diary—nobody needs to see your secrets. Step-by-Step Guide: Creating Least Privilege IAM Roles Here’s how you can create and assign least privilege IAM roles for Terraform: Step 1: Identify the specific actions Terraform needs to perform. For example, does it need to manage S3 buckets, create EC2 instances, or update Lambda functions? Step 2: Create a custom IAM policy that includes only those actions. Use AWS documentation to find the exact permissions required for each resource. Step 3: Assign the custom policy to an IAM role and attach that role to the Terraform process (e.g., through an EC2 instance profile or directly in your CI/CD pipeline). Step 4: Test the setup with a dry run. If Terraform complains about missing permissions, add only what’s necessary—don’t just slap on AdministratorAccess and call it a day! Here’s an example of a minimal IAM policy for managing S3 buckets: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:CreateBucket", "s3:DeleteBucket", "s3:PutObject", "s3:GetObject" ], "Resource": "arn:aws:s3:::your-bucket-name/*" } ] } 💡 Pro Tip: Use 


---
## Pod Security Standards: A Security-First Guide

- URL: https://orthogonal.info/pod-security-standards-a-security-first-guide/
- Date: 2026-03-30
- Category: DevOps
- Summary: Kubernetes Pod Security Standards 📌 TL;DR: I enforce PSS restricted on all production namespaces: runAsNonRoot: true, allowPrivilegeEscalation: false, all capabilities dropped, read-only root filesystem. Start with warn mode to find violations, then switch to enforce. This single change blocks the m

Kubernetes Pod Security Standards 📌 TL;DR: I enforce PSS restricted on all production namespaces: runAsNonRoot: true, allowPrivilegeEscalation: false, all capabilities dropped, read-only root filesystem. Start with warn mode to find violations, then switch to enforce. This single change blocks the majority of container escape attacks. 🎯 Quick Answer: Enforce Pod Security Standards (PSS) at the restricted level on all production namespaces: require runAsNonRoot, block privilege escalation with allowPrivilegeEscalation: false, and mount root filesystems as read-only. Imagine this: your Kubernetes cluster is humming along nicely, handling thousands of requests per second. Then, out of nowhere, you discover that one of your pods has been compromised. The attacker exploited a misconfigured pod to escalate privileges and access sensitive data. If this scenario sends chills down your spine, you’re not alone. Kubernetes security is a moving target, and Pod Security Standards (PSS) are here to help. Pod Security Standards are Kubernetes’ answer to the growing need for solid, declarative security policies. They provide a framework for defining and enforcing security requirements for pods, ensuring that your workloads adhere to best practices. But PSS isn’t just about ticking compliance checkboxes—it’s about aligning security with DevSecOps principles, where security is baked into every stage of the development lifecycle. Kubernetes security policies have evolved significantly over the years. From PodSecurityPolicy (deprecated in Kubernetes 1.21) to the introduction of Pod Security Standards, the focus has shifted toward simplicity and usability. PSS is designed to be developer-friendly while still offering powerful controls to secure your workloads. At its core, PSS is about enabling teams to adopt a “security-first” mindset. This means not only protecting your cluster from external threats but also mitigating risks posed by internal misconfigurations. By enforcing security policies at the namespace level, PSS ensures that every pod deployed adheres to predefined security standards, reducing the likelihood of accidental exposure. For example, consider a scenario where a developer unknowingly deploys a pod with an overly permissive security context, such as running as root or using the host network. Without PSS, this misconfiguration could go unnoticed until it’s too late. With PSS, such deployments can be blocked or flagged for review, ensuring that security is never compromised. 💡 From experience: Run kubectl label ns YOUR_NAMESPACE pod-security.kubernetes.io/warn=restricted first. This logs warnings without blocking deployments. Review the warnings for 1-2 weeks, fix the pod specs, then switch to enforce. I’ve migrated clusters with 100+ namespaces using this process with zero downtime. Key Challenges in Securing Kubernetes Pods Pod security doesn’t exist in isolation—network policies and service mesh provide the complementary network-level controls you need. Securing Kubernetes pods is easier said than done. Pods are the atomic unit of Kubernetes, and their configurations can be a goldmine for attackers if not properly secured. Common vulnerabilities include overly permissive access controls, unbounded resource limits, and insecure container images. These misconfigurations can lead to privilege escalation, denial-of-service attacks, or even full cluster compromise. The core tension: developers want their pods to “just work,” and adding runAsNonRoot: true or dropping capabilities breaks applications that assume root access. I’ve seen teams disable PSS entirely because one service needed NET_BIND_SERVICE. The fix isn’t to weaken the policy — it’s to grant targeted exceptions via a namespace with Baseline level for that specific workload, while keeping Restricted everywhere else. Consider the infamous Tesla Kubernetes breach in 2018, where attackers exploited a misconfigured pod to mine cryptocurrency. The pod had access to sensitive credentials stored in environment variables, and the cluster lacked proper monitoring. This incident underscores the importance of securing pod configurations from the outset. Another challenge is the dynamic nature of Kubernetes environments. Pods are ephemeral, meaning they can be created and destroyed in seconds. This makes it difficult to apply traditional security practices, such as manual reviews or static configurations. Instead, organizations must adopt automated tools and processes to ensure consistent security across their clusters. For instance, a common issue is the use of default service accounts, which often have more permissions than necessary. Attackers can exploit these accounts to move laterally within the cluster. By implementing PSS and restricting service account permissions, you can minimize this risk and ensure that pods only have access to the resources they truly need. ⚠️ Common Pitfall: Ignoring resource limits in pod configurations can lead to denial-of-service attacks. Always define resources.limits and resources.requests in your pod manifests to prevent resource exhaustion. Implementing Pod Security Standards in Production Before enforcing pod-level standards, make sure your container images are hardened—start with Docker container security best practices. So, how do you implement Pod Security Standards effectively? Let’s break it down step by step: Understand the PSS levels: Kubernetes defines three Pod Security Standards levels—Privileged, Baseline, and Restricted. Each level represents a stricter set of security controls. Start by assessing your workloads and determining which level is appropriate. Apply labels to namespaces: PSS operates at the namespace level. You can enforce specific security levels by applying labels to namespaces. For example: apiVersion: v1 kind: Namespace metadata: name: secure-apps labels: pod-security.kubernetes.io/enforce: restricted pod-security.kubernetes.io/audit: baseline pod-security.kubernetes.io/warn: baseline Audit and monitor: Use Kubernetes audit logs to monitor compliance. The audit and warn labels help identify pods that violate security policies without blocking them outright. Supplement with OPA/Gatekeeper for custom rules: PSS covers the basics, but you’ll need Gatekeeper for custom policies like “no images from Docker Hub” or “all pods must have resource limits.” Deploy Gatekeeper’s constraint templates for the rules PSS doesn’t cover — in my clusters, I run 12 custom Gatekeeper constraints on top of PSS. The migration path I use: Week 1: apply warn=restricted to all production namespaces. Week 2: collect and triage warnings — fix pod specs that can be fixed, identify workloads that genuinely need exceptions. Week 3: move fixed namespaces to enforce=restricted, exception namespaces to enforce=baseline. Week 4: add CI validation with kube-score to catch new violations before they hit the cluster. For development namespaces, I use enforce=baseline (not privileged). Even in dev, you want to catch the most dangerous misconfigurations. Developers should see PSS violations in dev, not discover them when deploying to production. CI integration is non-negotiable: run kubectl --dry-run=server against a namespace with enforce=restricted in your pipeline. If the manifest would be rejected, fail the build. This catches violations at PR time, not deploy time. 💡 Pro Tip: Use kubectl explain to understand the impact of PSS labels on your namespaces. It’s a lifesaver when debugging policy violations. Battle-Tested Strategies for Security-First Kubernetes Deployments Over the years, I’ve learned a few hard lessons about securing Kubernetes in production. Here are some battle-tested strategies: Integrate PSS into CI/CD pipelines: Shift security left by validating pod configurations during the build stage. Tools like kube-score and kubesec can analyze your manifests for security risks. Monitor pod activity: Use tools like Falco to detect suspicious activity in real-time. Fo


---
## Secrets Management in Kubernetes: A Security-First Guide

- URL: https://orthogonal.info/secrets-management-in-kubernetes-a-security-first-guide/
- Date: 2026-03-30
- Category: DevOps
- Summary: Secrets Management in Kubernetes 📌 TL;DR: Kubernetes Secrets are base64-encoded, not encrypted. Enable etcd encryption with aescbc, use External Secrets Operator to sync from Vault or your cloud KMS, set RBAC to restrict Secret access per namespace, and rotate credentials on 24-hour TTLs with Vault 

Secrets Management in Kubernetes 📌 TL;DR: Kubernetes Secrets are base64-encoded, not encrypted. Enable etcd encryption with aescbc, use External Secrets Operator to sync from Vault or your cloud KMS, set RBAC to restrict Secret access per namespace, and rotate credentials on 24-hour TTLs with Vault dynamic secrets. This is the exact stack I run in production. 🎯 Quick Answer: Kubernetes Secrets are only base64-encoded, not encrypted. Enable etcd encryption at rest and use External Secrets Operator to sync secrets from Vault or AWS Secrets Manager—never store sensitive values directly in Git manifests. Did you know that 60% of Kubernetes clusters in production are vulnerable to secrets exposure due to misconfigurations? That statistic from a recent CNCF report should send shivers down the spine of any security-conscious engineer. In Kubernetes, secrets are the keys to your kingdom—API tokens, database credentials, and encryption keys. When mishandled, they become the easiest entry point for attackers. Secrets management in Kubernetes is critical, but it’s also notoriously challenging. Kubernetes provides a native Secret resource, but relying solely on it can lead to security gaps. Secrets stored in etcd are base64-encoded, not encrypted by default, and without proper access controls, they’re vulnerable to unauthorized access. Add to that the complexity of managing secrets across multiple environments, and you’ve got a recipe for disaster. In this guide, we’ll explore production-proven strategies for managing secrets securely in Kubernetes. We’ll dive into tools like HashiCorp Vault and External Secrets Operator, discuss best practices, and share lessons learned from real-world deployments. Let’s get started. Before diving into tools and techniques, it’s important to understand the risks associated with poor secrets management. For example, a misconfigured Kubernetes cluster could expose sensitive environment variables to every pod in the namespace. This creates a situation where a compromised pod could escalate its privileges by accessing secrets it was never intended to use. Such scenarios are not hypothetical—they’ve been observed in real-world breaches. Furthermore, secrets management is not just about security; it’s also about scalability. As your Kubernetes environment grows, managing secrets manually becomes increasingly unfeasible. This is where automation and integration with external tools become essential. By the end of this guide, you’ll have a clear roadmap for implementing a scalable, secure secrets management strategy. 💡 From experience: Run kubectl get secrets --all-namespaces -o json | jq '.items[] | {namespace: .metadata.namespace, name: .metadata.name, type: .type}' to inventory every secret in your cluster. Then check which ones are actually used: compare against pod specs with envFrom and volumeMount references. I typically find 30-40% of secrets are orphaned and should be deleted. Vault: A Secure Foundation for Secrets Management HashiCorp Vault is often the first name that comes to mind when discussing secrets management. Why? Because it’s designed with security-first principles. Vault provides a centralized system for storing, accessing, and dynamically provisioning secrets. Unlike Kubernetes’ native Secret resources, Vault encrypts secrets at rest and in transit, ensuring they’re protected from prying eyes. One of Vault’s standout features is its ability to generate dynamic secrets. For example, instead of storing a static database password, Vault can create temporary credentials with a limited lifespan. This drastically reduces the attack surface and ensures secrets are rotated automatically. Integrating Vault with Kubernetes is straightforward, thanks to the Vault Agent Injector. This tool automatically injects secrets into pods as environment variables or files. Here’s a simple example of configuring Vault to inject secrets: apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: template: metadata: annotations: vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/agent-inject-secret-db-creds: "database/creds/my-role" spec: containers: - name: my-app image: my-app:latest env: - name: DB_USER valueFrom: secretKeyRef: name: vault-secret key: username - name: DB_PASSWORD valueFrom: secretKeyRef: name: vault-secret key: password Beyond basic integration, Vault supports advanced features like access policies and namespaces. Access policies allow you to define granular permissions for secrets, ensuring that only authorized users or applications can access specific data. For example, you can create a policy that allows a microservice to access only the database credentials it needs, while restricting access to other secrets. Namespaces, on the other hand, are useful for multi-tenant environments. They allow you to isolate secrets and policies for different teams or projects, providing an additional layer of security and organizational clarity. ⚠️ Security Note: Always enable Vault’s audit logging to track access to secrets. This is invaluable for compliance and incident response. 💡 Pro Tip: Use Vault’s dynamic secrets feature to minimize the risk of credential leakage. For example, configure Vault to generate short-lived database credentials that expire after a few hours. When troubleshooting Vault integration, common issues include misconfigured authentication methods and network connectivity problems. For example, if your Kubernetes pods can’t authenticate with Vault, check whether the Kubernetes authentication method is enabled and properly configured in Vault. Additionally, ensure that your Vault server is accessible from your Kubernetes cluster, and verify that the necessary firewall rules are in place. External Secrets Operator: Simplifying Secrets in Kubernetes While Vault is powerful, managing its integration with Kubernetes can be complex. Enter External Secrets Operator (ESO), an open-source tool that bridges the gap between external secrets providers (like Vault, AWS Secrets Manager, or Google Secret Manager) and Kubernetes. ESO works by syncing secrets from external providers into Kubernetes as Secret resources. This allows you to use the security features of external systems while maintaining compatibility with Kubernetes-native workflows. Here’s an example of configuring ESO to pull secrets from Vault: apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: my-secret spec: refreshInterval: "1h" secretStoreRef: name: vault-backend kind: SecretStore target: name: my-k8s-secret creationPolicy: Owner data: - secretKey: username remoteRef: key: database/creds/my-role property: username - secretKey: password remoteRef: key: database/creds/my-role property: password With ESO, you can automate secrets synchronization, reduce manual overhead, and ensure your Kubernetes secrets are always up-to-date. This is particularly useful in dynamic environments where secrets change frequently, such as when using Vault’s dynamic secrets feature. Another advantage of ESO is its support for multiple secret stores. For example, you can use Vault for database credentials, AWS Secrets Manager for API keys, and Google Secret Manager for encryption keys—all within the same Kubernetes cluster. This flexibility makes ESO a versatile tool for modern, multi-cloud environments. 💡 Pro Tip: Use ESO’s refresh interval to rotate secrets frequently. This minimizes the risk of stale credentials being exploited. When troubleshooting ESO, common issues include misconfigured secret store references and insufficient permissions. For example, if ESO fails to sync a secret from Vault, check whether the secret store reference is correct and whether the Vault token has the necessary permissions to access the secret. Additionally, ensure that the ESO controller has the required Kubernetes RBAC permissions to create and update Secret resources. Best Practices for Secrets Management in Production Managing secrets securely in pr


---
## Enterprise Security at Home: Wazuh & Suricata Setup

- URL: https://orthogonal.info/enterprise-security-at-home-wazuh-suricata-setup/
- Date: 2026-03-30
- Category: Homelab
- Summary: I run Wazuh and Suricata on my home network. Yes, enterprise SIEM and IDS for a homelab—it’s overkill by any reasonable measure. But after catching an IoT camera phoning home to servers in three different countries, I stopped second-guessing the investment. Here’s why I do it and how you can set it 

I run Wazuh and Suricata on my home network. Yes, enterprise SIEM and IDS for a homelab—it’s overkill by any reasonable measure. But after catching an IoT camera phoning home to servers in three different countries, I stopped second-guessing the investment. Here’s why I do it and how you can set it up too. Self-Hosted Security 📌 TL;DR: Learn how to deploy a self-hosted security stack using Wazuh and Suricata to bring enterprise-grade security practices to your homelab. 🎯 Quick Answer Learn how to deploy a self-hosted security stack using Wazuh and Suricata to bring enterprise-grade security practices to your homelab. 🏠 My setup: Wazuh SIEM + Suricata IDS on TrueNAS SCALE · 64GB ECC RAM · dual 10GbE NICs · OPNsense firewall · 4 VLANs · UPS-protected infrastructure · 30+ monitored Docker containers. It started with a simple question: “How secure is my homelab?” I had spent years designing enterprise-grade security systems, but my personal setup was embarrassingly basic. No intrusion detection, no endpoint monitoring—just a firewall and some wishful thinking. It wasn’t until I stumbled across a suspicious spike in network traffic that I realized I needed to practice what I preached. Homelabs are often overlooked when it comes to security. After all, they’re not hosting critical business applications, right? But here’s the thing: homelabs are a playground for experimentation, and that experimentation often involves sensitive data, credentials, or even production-like environments. If you’re like me, you want your homelab to be secure, not just functional. In this article, we’ll explore how to bring enterprise-grade security practices to your homelab using two powerful tools: Wazuh and Suricata. Wazuh provides endpoint monitoring and log analysis, while Suricata offers network intrusion detection. Together, they form a solid security stack that can help you detect and respond to threats effectively—even in a small-scale environment. Why does this matter? Cybersecurity threats are no longer limited to large organizations. Attackers often target smaller, less-secure environments as stepping stones to larger networks. Your homelab could be a weak link if left unprotected. Implementing a security stack like Wazuh and Suricata not only protects your data but also provides hands-on experience with tools used in professional environments. Additionally, a secure homelab allows you to experiment freely without worrying about exposing sensitive information. Whether you’re testing new software, running virtual machines, or hosting personal projects, a solid security setup ensures that your environment remains safe from external threats. 💡 Pro Tip: Treat your homelab as a miniature enterprise. Document your architecture, implement security policies, and regularly review your setup to identify potential vulnerabilities. Setting Up Wazuh for Endpoint Monitoring Wazuh is an open-source security platform designed for endpoint monitoring, log analysis, and intrusion detection. Think of it as your security operations center in a box. It’s highly scalable, but more importantly, it’s flexible enough to adapt to homelab setups. To get started, you’ll need to deploy the Wazuh server and agent. The server collects and analyzes data, while the agent runs on your endpoints to monitor activity. Here’s how to set it up: Step-by-Step Guide to Deploying Wazuh 1. Install the Wazuh server: # Install Wazuh repository curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | sudo apt-key add - echo "deb https://packages.wazuh.com/4.x/apt stable main" | sudo tee /etc/apt/sources.list.d/wazuh.list # Update packages and install Wazuh sudo apt update sudo apt install wazuh-manager 2. Configure the Wazuh agent on your endpoints: # Install Wazuh agent sudo apt install wazuh-agent # Configure agent to connect to the server sudo nano /var/ossec/etc/ossec.conf # Add your server's IP in the <server-ip> field # Start the agent sudo systemctl start wazuh-agent 3. Set up the Wazuh dashboard for visualization: # Install Wazuh dashboard sudo apt install wazuh-dashboard # Access the dashboard at http://<your-server-ip>:5601 Once deployed, you can configure alerts and dashboards to monitor endpoint activity. For example, you can set rules to detect unauthorized access attempts or suspicious file changes. Wazuh also integrates with cloud services like AWS and Azure, making it a versatile tool for hybrid environments. For advanced setups, you can enable file integrity monitoring (FIM) to track changes to critical files. This is particularly useful for detecting unauthorized modifications to configuration files or sensitive data. 💡 Pro Tip: Use TLS to secure communication between the Wazuh server and agents. The default setup is functional but not secure for production-like environments. Refer to the Wazuh documentation for detailed instructions on enabling TLS. Common troubleshooting issues include connectivity problems between the server and agents. Ensure that your firewall allows traffic on the required ports (default is 1514 for UDP and 1515 for TCP). If agents fail to register, double-check the server IP and authentication keys in the configuration file. ⚠️ What went wrong for me: My first Wazuh deployment ate 12GB of RAM and brought my TrueNAS box to a crawl. I hadn’t tuned the log ingestion rate or disabled unnecessary modules. After switching to a lightweight config—disabling cloud integrations I didn’t need and limiting log retention to 30 days—it runs comfortably on 4GB. Start lean and add monitoring rules as you need them. Deploying Suricata for Network Intrusion Detection Suricata is an open-source network intrusion detection system (NIDS) that analyzes network traffic for malicious activity. If Wazuh is your eyes on the endpoints, Suricata is your ears on the network. Together, they provide full coverage. Here’s how to deploy Suricata in your homelab: Installing and Configuring Suricata 1. Install Suricata: # Install Suricata sudo apt update sudo apt install suricata # Verify installation suricata --version 2. Configure Suricata to monitor your network interface: # Edit Suricata configuration sudo nano /etc/suricata/suricata.yaml # Set the network interface to monitor (e.g., eth0) - interface: eth0 3. Start Suricata: # Start Suricata service sudo systemctl start suricata Once Suricata is running, you can create custom rules to detect specific threats. For example, you might want to flag outbound traffic to known malicious IPs or detect unusual DNS queries. Suricata’s rule syntax is similar to Snort, making it easy to adapt existing rulesets. To enhance detection capabilities, consider integrating Suricata with Emerging Threats (ET) rules. These community-maintained rulesets are updated frequently to address new threats. You can download and update ET rules using the following command: # Download Emerging Threats rules sudo apt install oinkmaster sudo oinkmaster -C /etc/oinkmaster.conf -o /etc/suricata/rules ⚠️ Security Note: Suricata’s default ruleset is a good starting point, but it’s not exhaustive. Regularly update your rules and customize them based on your environment. Common pitfalls include misconfigured network interfaces and outdated rulesets. If Suricata fails to start, check the logs for errors related to the YAML configuration file. Ensure that the specified network interface exists and is active. Integrating Wazuh and Suricata for a Unified Stack Now that you have Wazuh and Suricata set up, it’s time to integrate them into a unified security stack. The goal is to correlate endpoint and network data for more actionable insights. Here’s how to integrate the two tools: Steps to Integration 1. Configure Wazuh to ingest Suricata logs: # Point Wazuh to Suricata logs sudo nano /var/ossec/etc/ossec.conf # Add a log collection entry for Suricata <localfile> <location>/var/log/suricata/eve.json</location> <log_format>json</log_format> </localfile> 2. Visualize Suricata data in the Wazuh dash


---
## Master Docker Container Security: Best Practices for 2026

- URL: https://orthogonal.info/master-docker-container-security-best-practices-for-2026/
- Date: 2026-03-30
- Category: Deep Dives
- Summary: Your staging environment is a dream. Every container spins up flawlessly, logs are clean, and your app hums along like a well-oiled machine. Then comes production. Suddenly, your containers are spewing errors faster than you can say “debug,” secrets are leaking like a sieve, and you’re frantically G

Your staging environment is a dream. Every container spins up flawlessly, logs are clean, and your app hums along like a well-oiled machine. Then comes production. Suddenly, your containers are spewing errors faster than you can say “debug,” secrets are leaking like a sieve, and you’re frantically Googling “Docker security best practices” while your team pings you with increasingly panicked messages. Sound familiar? If you’ve ever felt the cold sweat of deploying vulnerable containers or struggled to keep your secrets, well, secret, you’re not alone. In this article, we’ll dive into the best practices for mastering Docker container security in 2026. From locking down your images to managing secrets like a pro, I’ll help you turn your containerized chaos into a fortress of stability. Let’s make sure your next deployment doesn’t come with a side of heartburn. Introduction: Why Docker Security Matters in 2026 📌 TL;DR: After hardening 200+ production containers, this is my exact checklist: scan images with Trivy in CI (fail on HIGH+), run as non-root, drop all capabilities, mount read-only filesystems, and never bake secrets into image layers. These five controls block the majority of container attacks I’ve seen in production. 🎯 Quick Answer After hardening 200+ production containers, this is my exact checklist: scan images with Trivy in CI (fail on HIGH+), run as non-root, drop all capabilities, mount read-only filesystems, and never bake secrets into image layers. These five controls block the majority of container attacks I’ve seen i Ah, Docker—the magical box that lets us ship software faster than my morning coffee brews. If you’re a DevOps engineer, you’ve probably spent more time with Docker than with your family (no judgment, I’m guilty too). But as we barrel into 2026, the security landscape around Docker containers is evolving faster than my excuses for skipping gym day. Let’s face it: Docker has become the backbone of modern DevOps workflows. It’s everywhere, from development environments to production deployments. But here’s the catch—more containers mean more opportunities for security vulnerabilities to sneak in. It’s like hosting a party where everyone brings their own snacks, but some guests might smuggle in rotten eggs. Gross, right? Emerging security challenges in containerized environments are no joke. Attackers are getting smarter, and misconfigured containers or unscanned images can become ticking time bombs. If you’re not scanning your Docker images or using rootless containers, you’re basically leaving your front door wide open with a neon sign that says, “Hack me, I dare you.” 💡 Pro Tip: Start using image scanning tools to catch vulnerabilities early. It’s like running a background check on your containers before they move in. Proactive security measures aren’t just a nice-to-have anymore—they’re a must-have for production deployments. Trust me, nothing ruins a Friday night faster than a container breach. So buckle up, because in 2026, Docker security isn’t just about keeping things running; it’s about keeping them safe, too. Securing Container Images: Best Practices and Tools Let’s talk about securing container images—because nothing ruins your day faster than deploying a container that’s as vulnerable as a piñata at a kid’s birthday party. If you’re a DevOps engineer working with Docker containers in production, you already know that container security is no joke. But don’t worry, I’m here to make it just a little less painful (and maybe even fun). First things first: why is image scanning so important? Well, think of your container images like a lunchbox. You wouldn’t pack a sandwich that’s been sitting out for three days, right? Similarly, you don’t want to deploy a container image full of vulnerabilities. Image scanning tools help you spot those vulnerabilities before they make it into production, saving you from potential breaches, compliance violations, and awkward conversations with your security team. Now, let’s dive into some popular image scanning tools that can help you keep your containers squeaky clean: Trivy: A lightweight, open-source scanner that’s as fast as it is effective. It scans for vulnerabilities in OS packages, application dependencies, and even Infrastructure-as-Code files. Clair: A tool from the folks at CoreOS (now part of Red Hat) that specializes in static analysis of vulnerabilities in container images. Docker Security Scanning: Built right into Docker Hub, this tool automatically scans your images for known vulnerabilities. It’s like having a security guard at the door of your container registry. So, how do you integrate image scanning into your CI/CD pipeline without feeling like you’re adding another chore to your to-do list? It’s simpler than you think! Most image scanning tools offer CLI options or APIs that you can plug directly into your pipeline. Here’s a quick example using Trivy: # Add Trivy to your CI/CD pipeline # Step 1: Download the Trivy install script curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh -o install_trivy.sh # Step 2: Verify the script's integrity (e.g., using a checksum or GPG signature) # Example: echo "<expected-checksum> install_trivy.sh" | sha256sum -c - # Step 3: Execute the script after verification sh install_trivy.sh # Step 4: Scan your Docker image trivy image my-docker-image:latest # Step 5: Fail the build if vulnerabilities are found if [ $? -ne 0 ]; then echo "Vulnerabilities detected! Failing the build." exit 1 fi 💡 From experience: Set USER nonroot in every Dockerfile and add --cap-drop=ALL at runtime. If a specific capability is needed (e.g., NET_BIND_SERVICE for port 80), add it back explicitly with --cap-add. This single change would have prevented 3 out of the last 5 container escalation incidents I’ve investigated. In conclusion, securing your container images isn’t just a nice-to-have—it’s a must-have. By using image scanning tools like Trivy, Clair, or Docker Security Scanning and integrating them into your CI/CD pipeline, you can sleep a little easier knowing your containers are locked down tighter than a bank vault. And remember, security is a journey, not a destination. So keep scanning, keep learning, and keep those containers safe! 🛠️ Recommended Resources: Tools and books mentioned in (or relevant to) this article: Container Security — Fundamental technology concepts for protecting containerized apps ($40-50) Kubernetes in Action, 2nd Edition — The definitive guide to deploying and managing K8s in production ($45-55) Docker Deep Dive — Zero to Docker in one book — by Docker Captain Nigel Poulton ($30-40) The Complete Homelab Guide — Build your self-hosted infrastructure from scratch ($25-35) Secrets Management in Docker: Avoiding Common Pitfalls Let’s talk secrets management in Docker. If you’ve ever found yourself hardcoding a password into your container image, congratulations—you’ve just created a ticking time bomb. Managing secrets in containerized environments is like trying to keep a diary in a house full of nosy roommates. It’s tricky, but with the right tools and practices, you can keep your secrets safe and sound. First, let’s address the challenges. Containers are ephemeral by nature, spinning up and down faster than your caffeine buzz during a late-night deployment. This makes it hard to securely store and access sensitive data like API keys, database passwords, or encryption keys. Worse, if you bake secrets directly into your Docker images, anyone with access to those images can see them. It’s like hiding your house key under the doormat—convenient, but not exactly secure. So, what’s the solution? Here are some best practices to avoid common pitfalls: Never hardcode secrets: Seriously, don’t do it. Use environment variables or secret management tools instead. Use Docker Secrets: Docker has a built-in secrets management feature that allows you to securely pass sensitive data to your containers. I


---
## Pre-IPO API: SEC Filings, SPACs & Lockup Data

- URL: https://orthogonal.info/pre-ipo-intelligence-api-real-time-sec-filings-spacs-lockup-data-for-developers/
- Date: 2026-03-29
- Category: Deep Dives
- Summary: I built the Pre-IPO Intelligence API because I needed this data for my own trading systems and couldn’t find it in one place. If you’re building fintech applications, trading bots, or investment research tools, you know the pain: pre-IPO data is fragmented across dozens of SEC filing pages, paywalle

I built the Pre-IPO Intelligence API because I needed this data for my own trading systems and couldn’t find it in one place. If you’re building fintech applications, trading bots, or investment research tools, you know the pain: pre-IPO data is fragmented across dozens of SEC filing pages, paywalled databases, and stale spreadsheets. The Pre-IPO Intelligence API solves this by delivering real-time SEC filings, SPAC tracking, lockup expiration calendars, and M&A intelligence through a single, developer-friendly REST API — available now on RapidAPI with a free tier to get started. In this deep dive, we’ll cover what the API offers across its 42 endpoints, walk through practical code examples in both cURL and Python, and explore real-world use cases for developers and quant engineers. Whether you’re building the next algorithmic trading system or a portfolio intelligence dashboard, this guide will get you up and running in minutes. What Is the Pre-IPO Intelligence API? 📌 TL;DR: If you’re building fintech applications, trading bots, or investment research tools, you know the pain: pre-IPO data is fragmented across dozens of SEC filing pages, paywalled databases, and stale spreadsheets. 🎯 Quick Answer If you’re building fintech applications, trading bots, or investment research tools, you know the pain: pre-IPO data is fragmented across dozens of SEC filing pages, paywalled databases, and stale spreadsheets. The Pre-IPO Intelligence API (v3.0.1) is a comprehensive financial data service that aggregates, normalizes, and serves pre-IPO market intelligence through 42 RESTful endpoints. It covers the full lifecycle of companies going public — from early-stage private valuations and S-1 filings through SPAC mergers, IPO pricing, lockup expirations, and post-IPO M&A activity. Unlike scraping SEC.gov yourself or paying five-figure annual fees for enterprise terminals, this API gives you structured, machine-readable JSON data with sub-second response times. It’s designed for developers who need to integrate pre-IPO intelligence into their applications without building an entire data pipeline from scratch. Key Capabilities at a Glance Company Intelligence: Search and retrieve detailed profiles on pre-IPO companies, including valuation history, funding rounds, and sector classification SEC Filing Monitoring: Real-time tracking of S-1, S-1/A, F-1, and prospectus filings with parsed key data points Lockup Expiration Calendar: Know exactly when insider selling restrictions expire — one of the most predictable catalysts for post-IPO price movement SPAC Tracking: Monitor active SPACs, merger targets, trust values, redemption rates, and deal timelines M&A Intelligence: Track merger and acquisition activity involving pre-IPO and recently-public companies Market Overview: Aggregate statistics on IPO pipeline health, sector trends, and market sentiment indicators Getting Started: Subscribe on RapidAPI The fastest way to start using the API is through RapidAPI. The freemium model lets you explore endpoints with generous rate limits before committing to a paid plan. Here’s how to get set up: Visit the Pre-IPO Intelligence API page on RapidAPI Click “Subscribe to Test” and select the free tier Copy your X-RapidAPI-Key from the dashboard Start making requests immediately — no credit card required for the free plan Once subscribed, you’ll have access to all 42 endpoints. The free tier includes enough requests for development and testing, while paid tiers unlock higher rate limits and priority support for production workloads. Core Endpoint Reference Let’s walk through the five core endpoint groups with practical examples. All endpoints return JSON and accept standard query parameters for filtering, pagination, and sorting. 1. Company Search & Profiles The /api/companies/search endpoint is your entry point for finding pre-IPO companies. It supports full-text search across company names, tickers, sectors, and descriptions. cURL Example curl -X GET "https://pre-ipo-intelligence.p.rapidapi.com/api/companies/search?q=artificial+intelligence&sector=technology&limit=10" \ -H "X-RapidAPI-Key: YOUR_RAPIDAPI_KEY" \ -H "X-RapidAPI-Host: pre-ipo-intelligence.p.rapidapi.com" Python Example import requests url = "https://pre-ipo-intelligence.p.rapidapi.com/api/companies/search" params = { "q": "artificial intelligence", "sector": "technology", "limit": 10 } headers = { "X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY", "X-RapidAPI-Host": "pre-ipo-intelligence.p.rapidapi.com" } response = requests.get(url, headers=headers, params=params) companies = response.json() for company in companies.get("results", []): print(f"{company['name']} — Valuation: ${company.get('valuation', 'N/A')}") print(f" Sector: {company.get('sector')} | Stage: {company.get('stage')}") print() The response includes rich metadata: company name, latest valuation estimate, funding stage, sector, key executives, and links to relevant SEC filings. This is the same data that powers our Pre-IPO Valuation Tracker for companies like SpaceX, OpenAI, and Anthropic. 2. SEC Filing Monitoring The /api/filings/recent endpoint delivers newly published SEC filings relevant to IPO-track companies. Stop polling EDGAR manually — let the API push structured filing data to your application. curl -X GET "https://pre-ipo-intelligence.p.rapidapi.com/api/filings/recent?type=S-1&days=7&limit=20" \ -H "X-RapidAPI-Key: YOUR_RAPIDAPI_KEY" \ -H "X-RapidAPI-Host: pre-ipo-intelligence.p.rapidapi.com" import requests url = "https://pre-ipo-intelligence.p.rapidapi.com/api/filings/recent" params = {"type": "S-1", "days": 7, "limit": 20} headers = { "X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY", "X-RapidAPI-Host": "pre-ipo-intelligence.p.rapidapi.com" } response = requests.get(url, headers=headers, params=params) filings = response.json() for filing in filings.get("results", []): print(f"[{filing['filed_date']}] {filing['company_name']}") print(f" Type: {filing['filing_type']} | URL: {filing['sec_url']}") print() Each filing record includes the company name, filing type (S-1, S-1/A, F-1, 424B, etc.), filing date, SEC URL, and extracted financial highlights such as proposed share price range, shares offered, and underwriters. This is invaluable for building IPO alert systems or AI-driven market signal pipelines. 3. Lockup Expiration Calendar The /api/lockup/calendar endpoint is a hidden gem for swing traders and quant funds. Lockup expirations — when insiders are first allowed to sell shares after an IPO — are among the most statistically significant and predictable events in equity markets. Studies consistently show that stocks decline an average of 1–3% around lockup expiry dates due to increased supply pressure. import requests from datetime import datetime, timedelta url = "https://pre-ipo-intelligence.p.rapidapi.com/api/lockup/calendar" params = { "start_date": datetime.now().strftime("%Y-%m-%d"), "end_date": (datetime.now() + timedelta(days=30)).strftime("%Y-%m-%d"), } headers = { "X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY", "X-RapidAPI-Host": "pre-ipo-intelligence.p.rapidapi.com" } response = requests.get(url, headers=headers, params=params) lockups = response.json() for event in lockups.get("results", []): shares_pct = event.get("shares_percent", "N/A") print(f"{event['expiry_date']} — {event['company_name']} ({event['ticker']})") print(f" Shares unlocking: {shares_pct}% of float") print(f" IPO Price: ${event.get('ipo_price')} | Current: ${event.get('current_price')}") print() This data pairs perfectly with a disciplined risk management framework. You can build automated alerts, backtest lockup-expiration strategies, or feed the calendar into a portfolio hedging system. 4. SPAC Tracking SPACs (Special Purpose Acquisition Companies) remain an important vehicle for companies going public, especially in sectors like clean energy, fintech, and AI. The /api/spac/active endpoint provides real-time tracking of active SPACs and their merger pipelines. curl -X GET "https://pre-ipo-i


---
## CVE-2025-53521: F5 BIG-IP APM RCE — CISA Deadline 3/30

- URL: https://orthogonal.info/f5-big-ip-apm-cve-2025-53521-rce-cisa-kev-deadline/
- Date: 2026-03-29
- Category: Deep Dives
- Summary: CVE-2025-53521 was reclassified from DoS to RCE with active exploitation confirmed. F5 BIG-IP APM vulnerability added to CISA KEV with March 30 deadline. Detection commands, IOC checks, and mitigation steps for your BIG-IP fleet.

I triaged this CVE for my own perimeter the moment it hit the KEV catalog. If you’re running F5 BIG-IP with APM, here’s what you need to know and do—fast. CVE-2025-53521 dropped into CISA’s Known Exploited Vulnerabilities catalog on March 27, and the remediation deadline is March 30. If you’re running F5 BIG-IP with Access Policy Manager (APM), this needs your attention right now. Here’s what makes this one ugly: F5 originally classified CVE-2025-53521 as a denial-of-service issue. That classification has since been upgraded to remote code execution (CVSS 9.3) after active exploitation was confirmed in the wild. A vulnerability that many teams deprioritized as “just a DoS” is actually giving attackers code execution on BIG-IP appliances. If your patching decision was based on the original advisory, your risk assessment is wrong. The Reclassification: From DoS to Full RCE 📌 TL;DR: CVE-2025-53521 dropped into CISA’s Known Exploited Vulnerabilities catalog on March 27, and the remediation deadline is March 30 . If you’re running F5 BIG-IP with Access Policy Manager (APM), this needs your attention right now. 🎯 Quick Answer CVE-2025-53521 dropped into CISA’s Known Exploited Vulnerabilities catalog on March 27, and the remediation deadline is March 30 . If you’re running F5 BIG-IP with Access Policy Manager (APM), this needs your attention right now. When F5 first published advisory K000156741, CVE-2025-53521 was described as a denial-of-service condition in BIG-IP APM. The attack vector was clear enough — a crafted request to the APM authentication endpoint could crash the Traffic Management Microkernel (TMM). Annoying, but many shops treated it as a lower-priority patch. That assessment turned out to be incomplete. Subsequent analysis revealed that the same attack primitive — the malformed request that triggers the TMM crash — can be chained with a memory corruption condition to achieve arbitrary code execution. F5 updated the advisory to reflect this, bumping the CVSS score to 9.3 and reclassifying the impact from availability-only to full confidentiality/integrity/availability compromise. The timing here matters. Organizations that triaged this as a medium-severity DoS during the initial disclosure window may have scheduled it for their next maintenance cycle. With active exploitation now confirmed and CISA setting a 3-day remediation deadline, “next maintenance cycle” is too late. What We Know About Active Exploitation CISA doesn’t add vulnerabilities to the KEV catalog on a whim. The KEV listing confirms that CVE-2025-53521 is being actively exploited in the wild. F5 has published indicators of compromise alongside the updated advisory. Based on the available intelligence, here’s what the attack chain looks like: Initial Access: Attacker sends a specially crafted request to the BIG-IP APM authentication endpoint (typically /my.policy or /f5-w-68747470733a2f2f... APM webtop URLs). Memory Corruption: The malformed input triggers a buffer handling error in TMM’s APM module, corrupting adjacent memory structures. Code Execution: The corruption is exploited to redirect execution flow, achieving arbitrary code execution in the TMM process context — which runs as root. Post-Exploitation: With root-level access on the BIG-IP, the attacker can intercept traffic, extract credentials from APM session tables, modify iRules, or pivot deeper into the network. The root-level execution context is what elevates this from bad to critical. TMM handles all data plane traffic on BIG-IP. Owning TMM means owning every connection flowing through the appliance — SSL termination keys, session cookies, authentication tokens, everything. Affected Versions and Configurations CVE-2025-53521 affects BIG-IP systems running Access Policy Manager. The key conditions: BIG-IP APM must be provisioned and active (if you’re only running LTM without APM, you’re not directly affected) The APM virtual server must be accessible to the attacker — which in most deployments means internet-facing All BIG-IP software versions prior to the patched releases listed in K000156741 are vulnerable Check whether APM is provisioned on your BIG-IP: # Check APM provisioning status tmsh list sys provision apm # If you see "level nominal" or "level dedicated", APM is active # If you see "level none", APM is not provisioned — you're not affected by this specific CVE Check your current BIG-IP version: # Show running software version tmsh show sys version # Show all installed software images tmsh show sys software status Immediate Detection: Are You Already Compromised? Given that exploitation is active and the vulnerability existed before many orgs patched it, assume-breach is the right posture. For a structured approach, see our incident response playbook guide. Here’s what to look for. Check TMM Core Files Exploitation of this vulnerability typically produces TMM crash artifacts. If your BIG-IP has been restarting TMM unexpectedly, that’s a red flag: # Check for recent TMM core dumps ls -la /var/core/ ls -la /shared/core/ # Review TMM restart history tmsh show sys tmm-info | grep -i restart # Check system logs for TMM crashes grep -i "tmm.*core\|tmm.*crash\|tmm.*restart" /var/log/ltm /var/log/apm | tail -50 Audit APM Session Logs Look for anomalous APM authentication patterns — particularly failed authentications with unusual payload sizes or malformed usernames: # Review APM logs for the past 72 hours grep -E "ERR|CRIT|WARNING" /var/log/apm | tail -100 # Look for unusual APM access patterns awk '/access_policy/ && /ERR/' /var/log/apm # Check for oversized requests hitting APM endpoints grep -i "request.*too.*large\|oversized\|malform" /var/log/ltm /var/log/apm Inspect iRules and Configuration Changes Post-exploitation activity often involves modifying iRules to maintain persistence or intercept credentials: # List all iRules and their modification timestamps tmsh list ltm rule recursive | grep -E "^ltm rule|last-modified" # Check for recently modified iRules (compare against your change management records) find /config -name "*.tcl" -mtime -7 -ls # Look for suspicious iRule content (credential harvesting patterns) tmsh list ltm rule recursive | grep -iE "HTTP::header|HTTP::cookie|HTTP::password|b64encode|log local0" Review Network-Level IOCs F5’s updated advisory K000156741 includes specific network indicators. Cross-reference your firewall and IDS logs against the published IOCs. At minimum, check for: # On your perimeter firewall or SIEM, search for: # - Unusual POST requests to /my.policy endpoints with oversized payloads # - Connections from your BIG-IP management interface to unexpected external IPs # - DNS queries from BIG-IP to domains not in your known-good list # On the BIG-IP itself, check outbound connections: netstat -an | grep ESTABLISHED | grep -v "$(tmsh list net self all | grep address | awk '{print $2}' | cut -d/ -f1 | tr ' ' '\|' | sed 's/|$//')" If your network assessment methodology needs updating, Chris McNab’s Network Security Assessment remains the standard reference for systematically auditing network infrastructure — including load balancers and application delivery controllers like BIG-IP. Full disclosure: affiliate link. Mitigation: What to Do Right Now Priority 1: Patch Apply the fixed version from F5’s advisory. This is the only complete remediation. For BIG-IP, the upgrade process: # Download the hotfix ISO from downloads.f5.com # Upload to BIG-IP: scp BIGIP-*.iso admin@<bigip-mgmt>:/shared/images/ # Install the hotfix (from BIG-IP CLI): tmsh install sys software hotfix BIGIP-*.iso volume HD1.2 # Verify installation tmsh show sys software status # Reboot to the patched volume tmsh reboot volume HD1.2 Critical note: If you’re running an HA pair, follow F5’s documented rolling upgrade procedure. Don’t just reboot both units simultaneously. Priority 2: If You Can’t Patch Immediately If a maintenance window isn’t available in the next 24 hours, apply these compensating controls: R


---
## Mastering Kubernetes Security: Network Policies &

- URL: https://orthogonal.info/mastering-kubernetes-security-network-policies-service-mesh/
- Date: 2026-03-28
- Category: DevOps
- Summary: Network policies are the single most impactful security control you can add to a Kubernetes cluster — and most clusters I audit don’t have a single one. After implementing network segmentation across enterprise clusters with hundreds of namespaces, I’ve developed a repeatable approach that works. He

Network policies are the single most impactful security control you can add to a Kubernetes cluster — and most clusters I audit don’t have a single one. After implementing network segmentation across enterprise clusters with hundreds of namespaces, I’ve developed a repeatable approach that works. Here’s the playbook I use. Introduction to Kubernetes Security Challenges 📌 TL;DR: Explore production-proven strategies for securing Kubernetes with network policies and service mesh, focusing on a security-first approach to DevSecOps. 🎯 Quick Answer Explore production-proven strategies for securing Kubernetes with network policies and service mesh, focusing on a security-first approach to DevSecOps. According to a recent CNCF survey, 67% of organizations now run Kubernetes in production, yet only 23% have implemented pod security standards. This statistic is both surprising and alarming, highlighting how many teams prioritize functionality over security in their Kubernetes environments. Kubernetes has become the backbone of modern infrastructure, enabling teams to deploy, scale, and manage applications with unprecedented ease. But with great power comes great responsibility—or in this case, great security risks. From misconfigured RBAC roles to overly permissive network policies, the attack surface of a Kubernetes cluster can quickly spiral out of control. If you’re like me, you’ve probably seen firsthand how a single misstep in Kubernetes security can lead to production incidents, data breaches, or worse. The good news? By adopting a security-first mindset and Using tools like network policies and service meshes, you can significantly reduce your cluster’s risk profile. One of the biggest challenges in Kubernetes security is the sheer complexity of the ecosystem. With dozens of moving parts—pods, nodes, namespaces, and external integrations—it’s easy to overlook critical vulnerabilities. For example, a pod running with excessive privileges or a namespace with unrestricted access can act as a gateway for attackers to compromise your entire cluster. Another challenge is the dynamic nature of Kubernetes environments. Applications are constantly being updated, scaled, and redeployed, which can introduce new security risks. Without robust monitoring and automated security checks, it’s nearly impossible to keep up with these changes and ensure your cluster remains secure. 💡 Pro Tip: Regularly audit your Kubernetes configurations using tools like kube-bench and kube-hunter. These tools can help you identify misconfigurations and vulnerabilities before they become critical issues. Network Policies: Building a Secure Foundation 🔍 Lesson learned: When I first deployed network policies in a production cluster, I locked out the monitoring stack — Prometheus couldn’t scrape metrics, Grafana dashboards went dark, and the on-call engineer thought the cluster was down. Always test with a canary namespace first, and explicitly allow your observability traffic before applying default-deny. Network policies are one of Kubernetes’ most underrated security features. They allow you to define how pods communicate with each other and with external services, effectively acting as a firewall within your cluster. Without network policies, every pod can talk to every other pod by default—a recipe for disaster in production. To implement network policies effectively, you need to start by understanding your application’s communication patterns. Which services need to talk to each other? Which ones should be isolated? Once you’ve mapped out these interactions, you can define network policies to enforce them. Here’s an example of a basic network policy that restricts ingress traffic to a pod: apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-specific-ingress namespace: my-namespace spec: podSelector: matchLabels: app: my-app policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: trusted-app ports: - protocol: TCP port: 8080 This policy ensures that only pods labeled app: trusted-app can send traffic to my-app on port 8080. It’s a simple yet powerful way to enforce least privilege. However, network policies can become complex as your cluster grows. For example, managing policies across multiple namespaces or environments can lead to configuration drift. To address this, consider using tools like Calico or Cilium, which provide advanced network policy management features and integrations. Another common use case for network policies is restricting egress traffic. For instance, you might want to prevent certain pods from accessing external resources like the internet. Here’s an example of a policy that blocks all egress traffic: apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-egress namespace: my-namespace spec: podSelector: matchLabels: app: my-app policyTypes: - Egress egress: [] This deny-all egress policy ensures that the specified pods cannot initiate any outbound connections, adding an extra layer of security. 💡 Pro Tip: Start with a default deny-all policy and explicitly allow traffic as needed. This forces you to think critically about what communication is truly necessary. Troubleshooting: If your network policies aren’t working as expected, check the network plugin you’re using. Not all plugins support network policies, and some may have limitations or require additional configuration. Service Mesh: Enhancing Security at Scale ⚠️ Tradeoff: A service mesh like Istio adds powerful security features (mTLS, traffic policies) but also adds significant operational complexity. Sidecar proxies consume memory and CPU on every pod. In resource-constrained clusters, I’ve seen the mesh overhead exceed 15% of total cluster resources. For smaller deployments, network policies alone may be the right call. While network policies are great for defining communication rules, they don’t address higher-level concerns like encryption, authentication, and observability. This is where service meshes come into play. A service mesh provides a layer of infrastructure for managing service-to-service communication, offering features like mutual TLS (mTLS), traffic encryption, and detailed telemetry. Popular service mesh solutions include Istio, Linkerd, and Consul. Each has its strengths, but Istio stands out for its strong security features. For example, Istio can automatically encrypt all traffic between services using mTLS, ensuring that sensitive data is protected even within your cluster. Here’s an example of enabling mTLS in Istio: apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: istio-system spec: mtls: mode: STRICT This configuration enforces strict mTLS for all services in the istio-system namespace. It’s a simple yet effective way to enhance security across your cluster. In addition to mTLS, service meshes offer features like traffic shaping, retries, and circuit breaking. These capabilities can improve the resilience and performance of your applications while also enhancing security. For example, you can use Istio’s traffic policies to limit the rate of requests to a specific service, reducing the risk of denial-of-service attacks. Another advantage of service meshes is their observability features. Tools like Jaeger and Kiali integrate smoothly with service meshes, providing detailed insights into service-to-service communication. This can help you identify and troubleshoot security issues, such as unauthorized access or unexpected traffic patterns. ⚠️ Security Note: Don’t forget to rotate your service mesh certificates regularly. Expired certificates can lead to downtime and security vulnerabilities. Troubleshooting: If you’re experiencing issues with mTLS, check the Istio control plane logs for errors. Common problems include misconfigured certificates or incompatible protocol versions. Integrating Network Policies and Service Mesh for Maximum Security Network policies and serv


---
## Zero Trust for Developers: Simplifying Security

- URL: https://orthogonal.info/zero-trust-for-developers-simplifying-security/
- Date: 2026-03-28
- Category: Security
- Summary: Most Zero Trust guides are theoretical whitepapers that never touch a real network. I’ve actually implemented Zero Trust on my home network — OPNsense firewall with micro-segmented VLANs, mTLS between services, and identity-based access for every endpoint. Here’s how I translate those same principle

Most Zero Trust guides are theoretical whitepapers that never touch a real network. I’ve actually implemented Zero Trust on my home network — OPNsense firewall with micro-segmented VLANs, mTLS between services, and identity-based access for every endpoint. Here’s how I translate those same principles into developer-friendly patterns that work in production. Introduction to Zero Trust 📌 TL;DR: Learn how to implement Zero Trust principles in a way that empowers developers to build secure systems without relying solely on security teams. Introduction to Zero Trust Everyone talks about Zero Trust like it’s the silver bullet for cybersecurity. 🎯 Quick Answer Learn how to implement Zero Trust principles in a way that empowers developers to build secure systems without relying solely on security teams. Introduction to Zero Trust Everyone talks about Zero Trust like it’s the silver bullet for cybersecurity. Everyone talks about Zero Trust like it’s the silver bullet for cybersecurity. But let’s be honest—most explanations are so abstract they might as well be written in hieroglyphics. “Never trust, always verify” is catchy, but what does it actually mean for developers writing code or deploying applications? Here’s the truth: Zero Trust isn’t just a security team’s responsibility—it’s a major change that developers need to embrace. Traditional security models relied on perimeter defenses: firewalls, VPNs, and the assumption that anything inside the network was safe. That worked fine in the days of monolithic applications hosted on-premises. But today? With microservices, cloud-native architectures, and remote work, the perimeter is gone. Attackers don’t care about your firewall—they’re targeting your APIs, your CI/CD pipelines, and your developers. Zero Trust flips the script. Instead of assuming trust based on location or network, it demands verification at every step. Identity, access, and data flow are scrutinized continuously. For developers, this means building systems where security isn’t bolted on—it’s baked in. And yes, that sounds overwhelming, but stick with me. By the end of this article, you’ll see how Zero Trust can help developers rather than frustrate them. Zero Trust is also a response to the evolving threat landscape. Attackers are increasingly sophisticated, Using techniques like phishing, supply chain attacks, and credential stuffing. These threats bypass traditional defenses, making a Zero Trust approach essential. For developers, this means designing systems that assume breaches will happen and mitigate their impact. Consider a real-world scenario: a developer deploys a microservice that communicates with other services via APIs. Without Zero Trust, an attacker who compromises one service could potentially access all others. With Zero Trust, each API call is authenticated and authorized, limiting the blast radius of a breach. 💡 Pro Tip: Think of Zero Trust as a mindset rather than a checklist. It’s about questioning assumptions and continuously verifying trust at every layer of your architecture. To get started, developers should familiarize themselves with foundational Zero Trust concepts like least privilege access, identity verification, and continuous monitoring. These principles will guide the practical steps discussed later . Why Developers Are Key to Zero Trust Success Let’s get one thing straight: Zero Trust isn’t just a security team’s problem. If you’re a developer, you’re on the front lines. Every line of code you write, every API you expose, every container you deploy—these are potential attack vectors. The good news? Developers are uniquely positioned to make Zero Trust work because they control the systems attackers are targeting. Here’s the reality: security teams can’t scale. They’re often outnumbered by developers 10 to 1, and their tools are reactive by nature. Developers, on the other hand, are proactive. By integrating security into the development lifecycle, you can catch vulnerabilities before they ever reach production. This isn’t just theory—it’s the essence of DevSecOps. Helping developers aligns perfectly with Zero Trust principles. When developers adopt practices like least privilege access, secure coding patterns, and automated security checks, they’re actively reducing the attack surface. It’s not about turning developers into security experts—it’s about giving them the tools and knowledge to make secure decisions without slowing down innovation. Take the example of API development. APIs are a common target for attackers because they often expose sensitive data or functionality. By implementing Zero Trust principles like strong authentication and authorization, developers can ensure that only legitimate requests are processed. This proactive approach prevents attackers from exploiting vulnerabilities. ⚠️ Common Pitfall: Developers sometimes assume that internal APIs are safe from external threats. In reality, attackers often exploit internal systems once they gain a foothold. Treat all APIs as untrusted. Another area where developers play a crucial role is container security. Containers are lightweight and portable, but they can also introduce risks if not properly secured. By using tools like Docker Content Trust and Kubernetes Pod Security Standards, developers can ensure that containers are both functional and secure. Practical Steps for Developers to Implement Zero Trust Zero Trust sounds great in theory, but how do you actually implement it as a developer? Let’s break it down into actionable steps: 1. Enforce Least Privilege Access ⚠️ Tradeoff: Strict least-privilege RBAC means developers can’t just kubectl exec into any pod to debug. This slows down incident response if you’re not prepared. I solve this with time-boxed elevated access via a simple approval workflow — developers get temporary admin for 30 minutes, fully audited. Start by ensuring every service, user, and application has the minimum permissions necessary to perform its tasks. This isn’t just a security best practice—it’s a core principle of Zero Trust. apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: read-only-role rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list"] In Kubernetes, for example, you can use RBAC (Role-Based Access Control) to define granular permissions like the example above. Notice how the role only allows read operations on pods—nothing more. ⚠️ Security Note: Avoid using wildcard permissions (e.g., “*”). They’re convenient but dangerous in production. Least privilege access also applies to database connections. For instance, a service accessing a database should only have permissions to execute specific queries it needs. This limits the damage an attacker can do if the service is compromised. 2. Implement Strong Identity Verification Identity is the cornerstone of Zero Trust. Every request should be authenticated and authorized, whether it’s coming from a user or a service. Use tools like OAuth2, OpenID Connect, or mutual TLS for service-to-service authentication. curl -X POST https://auth.example.com/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&client_id=your-client-id&client_secret=your-client-secret" In this example, a service requests an OAuth2 token using client credentials. This ensures that only authenticated services can access your APIs. 💡 Pro Tip: Rotate secrets and tokens regularly. Use tools like HashiCorp Vault or AWS Secrets Manager to automate this process. Mutual TLS is another powerful tool for identity verification. It ensures that both the client and server authenticate each other, providing an additional layer of security for service-to-service communication. 3. Integrate Security into CI/CD Pipelines Don’t wait until production to think about security. Automate security checks in your CI/CD pipelines to catch issues early. Tools like Snyk, Trivy, and Checkov can scan your code, containers, and infrastructure for


---
## Track Pre-IPO Valuations: SpaceX, OpenAI & More

- URL: https://orthogonal.info/how-to-track-pre-ipo-valuations-for-spacex-openai-and-anthropic-with-a-free-api/
- Date: 2026-03-27
- Category: Finance &amp; Trading
- Summary: Track implied valuations for SpaceX ($2T), OpenAI ($1.3T), and 19 more pre-IPO tech companies using a free API that analyzes publicly traded closed-end fund data.

SpaceX is being valued at $2 trillion by the market. OpenAI at $1.3 trillion. Anthropic at over $500 billion. But none of these companies are publicly traded. There’s no ticker symbol, no earnings call, no 10-K filing. So how do we know what the market thinks they’re worth? The answer lies in a fascinating financial instrument that most developers and even many finance professionals overlook: publicly traded closed-end funds that hold shares in pre-IPO companies. And now there’s a free pre-IPO valuation API that does all the math for you — turning raw fund data into real-time implied valuations for the world’s most anticipated IPOs. In this post, I’ll explain the methodology, walk you through the current data, and show you how to integrate this pre-IPO valuation tracker into your own applications using a few simple API calls. The Hidden Signal: How Public Markets Price Private Companies 📌 TL;DR: SpaceX is being valued at $2 trillion by the market. OpenAI at $1.3 trillion . Anthropic at over $500 billion . There are two closed-end funds trading on the NYSE that give us a direct window into how the public market values private tech companies: DXYZ (Destiny Tech100) — Holds shares in SpaceX, Stripe, Discord, and other pre-IPO giants VCX (10X Capital Venture Acquisition Corp) — Concentrated positions in OpenAI, Anthropic, Databricks, xAI, and more Unlike typical venture funds, these trade on public exchanges just like any stock. That means their share prices are set by supply and demand — real money from real investors making real bets on the future value of these private companies. Here’s the key insight: these funds publish their Net Asset Value (NAV) and their portfolio holdings (which companies they own, and what percentage of the fund each company represents). When the fund’s market price diverges from its NAV — and it almost always does — we can use that divergence to calculate what the market implicitly values each underlying private company at. The Math: From Fund Premium to Implied Valuation The calculation is straightforward. Let’s walk through it step by step: Step 1: Calculate the fund’s premium to NAV Fund Premium = (Market Price - NAV) / NAV Example (DXYZ): Market Price = $65.00 NAV per share = $8.50 Premium = ($65.00 - $8.50) / $8.50 = 665% Yes, you read that right. DXYZ routinely trades at 6-8x its net asset value. Investors are paying $65 for $8.50 worth of assets because they believe those assets (SpaceX, Stripe, etc.) are dramatically undervalued on the fund’s books. Step 2: Apply the premium to each holding Implied Valuation = Last Round Valuation × (1 + Fund Premium) × (Holding Weight Adjustment) Example (SpaceX via DXYZ): Last private round: $350B DXYZ premium: ~665% SpaceX weight in DXYZ: ~33% Implied Valuation ≈ $2,038B ($2.04 trillion) The API handles all of this automatically — pulling live prices, applying the latest NAV data, weighting by portfolio composition, and outputting a clean implied valuation for each company. The Pre-IPO Valuation Leaderboard: $7 Trillion in Implied Value Here’s the current leaderboard from the AI Stock Data API, showing the top implied valuations across both funds. These are real numbers derived from live market data: RankCompanyImplied ValuationFundLast Private RoundPremium to Last Round1SpaceX$2,038BDXYZ$350B+482%2OpenAI$1,316BVCX$300B+339%3Stripe$533BDXYZ$65B+720%4Databricks$520BVCX$43B+1,109%5Anthropic$516BVCX$61.5B+739% Across 21 tracked companies, the total implied market valuation exceeds $7 trillion. To put that in perspective, that’s roughly equivalent to the combined market caps of Apple and Microsoft. Some of the most striking data points: Databricks at +1,109% over its last round — The market is pricing in explosive growth in the enterprise data/AI platform space. At an implied $520B, Databricks would be worth more than most public SaaS companies combined. SpaceX at $2 trillion — Making it (by implied valuation) one of the most valuable companies on Earth, public or private. This reflects both Starlink’s revenue trajectory and investor excitement around Starship. Stripe’s quiet resurgence — At an implied $533B, the market has completely repriced Stripe from its 2023 down-round doldrums. The embedded finance thesis is back. The AI trio — OpenAI ($1.3T), Anthropic ($516B), and xAI together represent a massive concentration of speculative capital in foundation model companies. API Walkthrough: Get Pre-IPO Valuations in 30 Seconds The AI Stock Data API is available on RapidAPI with a free tier (500 requests/month) — no credit card required. Here’s how to get started. 1. Get the Valuation Leaderboard This single endpoint returns all tracked pre-IPO companies ranked by implied valuation: # Get the full pre-IPO valuation leaderboard (FREE tier) curl "https://ai-stock-data-api.p.rapidapi.com/companies/leaderboard" -H "X-RapidAPI-Key: YOUR_KEY" -H "X-RapidAPI-Host: ai-stock-data-api.p.rapidapi.com" Response includes company name, implied valuation, source fund, last private round valuation, premium percentage, and portfolio weight — everything you need to build a pre-IPO tracking dashboard. 2. Get Live Fund Quotes with NAV Premium Want to track the DXYZ fund premium or VCX fund premium in real time? The quote endpoint gives you the live price, NAV, premium percentage, and market data: # Get live DXYZ quote with NAV premium calculation curl "https://ai-stock-data-api.p.rapidapi.com/funds/DXYZ/quote" -H "X-RapidAPI-Key: YOUR_KEY" -H "X-RapidAPI-Host: ai-stock-data-api.p.rapidapi.com" # Get live VCX quote curl "https://ai-stock-data-api.p.rapidapi.com/funds/VCX/quote" -H "X-RapidAPI-Key: YOUR_KEY" -H "X-RapidAPI-Host: ai-stock-data-api.p.rapidapi.com" 3. Premium Analytics: Bollinger Bands & Mean Reversion For quantitative traders, the API offers Bollinger Band analysis on fund premiums — helping you identify when DXYZ or VCX is statistically overbought or oversold relative to its own history: # Premium analytics with Bollinger Bands (Pro tier) curl "https://ai-stock-data-api.p.rapidapi.com/funds/DXYZ/premium/bands" -H "X-RapidAPI-Key: YOUR_KEY" -H "X-RapidAPI-Host: ai-stock-data-api.p.rapidapi.com" The response includes the current premium, 20-day moving average, upper and lower Bollinger Bands (2σ), and a z-score telling you exactly how many standard deviations the current premium is from the mean. When the z-score exceeds +2 or drops below -2, you’re looking at a potential mean-reversion trade. 4. Build It Into Your App (JavaScript Example) // Fetch the pre-IPO valuation leaderboard const response = await fetch( 'https://ai-stock-data-api.p.rapidapi.com/companies/leaderboard', { headers: { 'X-RapidAPI-Key': process.env.RAPIDAPI_KEY, 'X-RapidAPI-Host': 'ai-stock-data-api.p.rapidapi.com' } } ); const leaderboard = await response.json(); // Display top 5 companies by implied valuation leaderboard.slice(0, 5).forEach((company, i) => { console.log( `${i + 1}. ${company.name}: $${company.implied_valuation_b}B ` + `(+${company.premium_pct}% vs last round)` ); }); # Python example: Track SpaceX valuation over time import requests headers = { "X-RapidAPI-Key": "YOUR_KEY", "X-RapidAPI-Host": "ai-stock-data-api.p.rapidapi.com" } # Get the leaderboard resp = requests.get( "https://ai-stock-data-api.p.rapidapi.com/companies/leaderboard", headers=headers ) companies = resp.json() # Filter for SpaceX spacex = next(c for c in companies if "SpaceX" in c["name"]) print(f"SpaceX implied valuation: ${spacex['implied_valuation_b']}B") print(f"Premium over last round: {spacex['premium_pct']}%") print(f"Source fund: {spacex['fund']}") Who Should Use This API? The Pre-IPO & AI Valuation Intelligence API is designed for several distinct audiences: Fintech Developers Building Pre-IPO Dashboards If you’re building an investment platform, portfolio tracker, or market intelligence tool, this API gives you data that simply doesn’t exist elsewhere in a structured format. Add a “Pre-IPO Watchlist” feature to your app and let u


---
## 5 Best Finance APIs for Tracking Pre-IPO Valuations in 2026

- URL: https://orthogonal.info/5-best-finance-apis-for-tracking-pre-ipo-valuations-in-2026/
- Date: 2026-03-27
- Category: Finance &amp; Trading
- Summary: Compare the top APIs for tracking private company valuations, including SpaceX, OpenAI, and Anthropic. Free and paid options for fintech developers.

Why Pre-IPO Valuation Tracking Matters in 2026 📌 TL;DR: Why Pre-IPO Valuation Tracking Matters in 2026 The private tech market has exploded. SpaceX is now valued at over $2 trillion by public markets, OpenAI at $1.3 trillion, and the total implied market cap of the top 21 pre-IPO companies exceeds $7 trillion . The private tech market has exploded. SpaceX is now valued at over $2 trillion by public markets, OpenAI at $1.3 trillion, and the total implied market cap of the top 21 pre-IPO companies exceeds $7 trillion. For developers building fintech applications, having access to this data via APIs is crucial. But here’s the problem: these companies are private. There’s no ticker symbol, no Bloomberg terminal feed, no Yahoo Finance page. So how do you get valuation data? The Closed-End Fund Method Two publicly traded closed-end funds — DXYZ (Destiny Tech100) and VCX (Fundrise Growth Tech) — hold shares in these private companies. They trade on the NYSE, publish their holdings weights, and report NAV periodically. By combining market prices with holdings data, you can derive implied valuations for each portfolio company. Top 5 Finance APIs for Pre-IPO Data 1. AI Stock Data API (Pre-IPO Intelligence) — Best Overall Price: Free tier (500 requests/mo) | Pro $19/mo | Ultra $59/mo Endpoints: 44 endpoints covering valuations, premium analytics, risk metrics Best for: Developers who need complete pre-IPO analytics This API tracks implied valuations for 21 companies across both VCX and DXYZ funds. The free tier includes the valuation leaderboard (SpaceX at $2T, OpenAI at $1.3T) and live fund quotes. Pro tier adds Bollinger Bands on NAV premiums, RSI signals, and historical data spanning 500+ trading days. curl "https://ai-stock-data-api.p.rapidapi.com/companies/leaderboard" \ -H "X-RapidAPI-Key: YOUR_KEY" \ -H "X-RapidAPI-Host: ai-stock-data-api.p.rapidapi.com" Try it free on RapidAPI → 2. Yahoo Finance API — Best for Public Market Data Price: Free tier available Best for: Getting live quotes for DXYZ and VCX (the funds themselves) Yahoo Finance gives you real-time price data for the publicly traded funds, but not the implied private company valuations. You’d need to build the valuation logic yourself. 3. SEC EDGAR API — Best for Filing Data Price: Free Best for: Accessing official SEC filings for fund holdings The SEC EDGAR API provides access to N-PORT and N-CSR filings where closed-end funds disclose their holdings. However, this data is quarterly and requires significant parsing. 4. PitchBook API — Best for Enterprise Price: Enterprise pricing (typically $10K+/year) Best for: VCs and PE firms with big budgets PitchBook has the most complete private company data, but it’s priced for institutional investors, not indie developers. 5. Crunchbase API — Best for Funding Rounds Price: Starts at $99/mo Best for: Tracking funding rounds and company profiles Crunchbase tracks funding rounds and valuations at the time of investment, but doesn’t provide real-time market-implied valuations. Comparison Table FeatureAI Stock DataYahoo FinanceSEC EDGARPitchBookCrunchbase Implied Valuations✅❌❌✅❌ Real-time Prices✅✅❌✅❌ Premium Analytics✅❌❌❌❌ Free Tier✅ (500/mo)✅✅❌❌ API on RapidAPI✅✅❌❌✅ Getting Started The fastest way to start tracking pre-IPO valuations is with the AI Stock Data API’s free tier: Sign up at RapidAPI Subscribe to the free Basic plan (500 requests/month) Call the leaderboard endpoint to see all 21 companies ranked by implied valuation Use the quote endpoint for real-time fund data with NAV premiums Disclaimer: Implied valuations are mathematical derivations based on publicly available fund data. They are not official company valuations and should not be used as investment advice. Both VCX and DXYZ trade at significant premiums to NAV. Real API Examples: From curl to Python Let's get practical. Here are real API calls you can run today to start pulling pre-IPO valuation data. I'll walk through curl for quick testing, then Python for building something more permanent. curl: Quick Leaderboard Check # Get the full valuation leaderboard curl -s "https://ai-stock-data-api.p.rapidapi.com/companies/leaderboard" \ -H "X-RapidAPI-Key: YOUR_KEY" \ -H "X-RapidAPI-Host: ai-stock-data-api.p.rapidapi.com" | python3 -m json.tool A typical response looks like this: { "leaderboard": [ { "rank": 1, "company": "SpaceX", "implied_valuation": "$2.01T", "fund_source": "DXYZ", "weight_pct": 28.5, "change_30d": "+12.3%" }, { "rank": 2, "company": "OpenAI", "implied_valuation": "$1.31T", "fund_source": "DXYZ", "weight_pct": 15.2, "change_30d": "+8.7%" }, { "rank": 3, "company": "Stripe", "implied_valuation": "$412B", "fund_source": "VCX", "weight_pct": 12.8, "change_30d": "-2.1%" } ], "metadata": { "last_updated": "2026-03-28T16:00:00Z", "total_companies": 21, "data_source": "SEC filings + market data" } } Python: Building a Tracking Dashboard import requests import pandas as pd RAPIDAPI_KEY = "your_key_here" BASE_URL = "https://ai-stock-data-api.p.rapidapi.com" HEADERS = { "X-RapidAPI-Key": RAPIDAPI_KEY, "X-RapidAPI-Host": "ai-stock-data-api.p.rapidapi.com" } def get_leaderboard(): "Fetch the pre-IPO valuation leaderboard." resp = requests.get(f"{BASE_URL}/companies/leaderboard", headers=HEADERS) resp.raise_for_status() return resp.json()["leaderboard"] def get_fund_quote(symbol): "Get real-time quote for DXYZ or VCX." resp = requests.get(f"{BASE_URL}/quote/{symbol}", headers=HEADERS) resp.raise_for_status() return resp.json() # Build a tracking dashboard leaderboard = get_leaderboard() df = pd.DataFrame(leaderboard) print(df[["rank", "company", "implied_valuation", "change_30d"]].to_string(index=False)) # Get live fund data with NAV premium for symbol in ["DXYZ", "VCX"]: quote = get_fund_quote(symbol) print(f"\n{symbol}: ${quote['price']:.2f} | NAV Premium: {quote['nav_premium']}%") SEC EDGAR: Free Holdings Data SEC EDGAR is completely free but requires a bit more work to parse. Here's how to pull the latest N-PORT filing for Destiny Tech100 (DXYZ): import requests # Get latest N-PORT filing for Destiny Tech100 (DXYZ) # CIK for Destiny Tech100 Inc: 0001515671 CIK = "0001515671" url = f"https://efts.sec.gov/LATEST/search-index?q=%22destiny+tech%22&dateRange=custom&startdt=2026-01-01&forms=N-PORT" headers = {"User-Agent": "MaxTrader [email protected]"} resp = requests.get(url, headers=headers) # SEC requires User-Agent header — they'll block you without one print(f"Found {resp.json().get('hits', {}).get('total', 0)} filings") Cost Comparison: What You'll Actually Pay Pricing is the elephant in the room. Here's what each API actually costs when you move past the free tier: APIFree TierStarterProEnterprise AI Stock Data API500 req/mo$9/mo (2,000 req)$19/mo (10,000 req)$59/mo (100,000 req) Yahoo Finance (via RapidAPI)500 req/mo$10/mo$25/moCustom SEC EDGARUnlimited (10 req/sec)——— PitchBookNone——~$15,000/yr CrunchbaseNone$99/mo$199/moCustom For an indie developer or small fintech startup, the realistic options are AI Stock Data API (best implied valuations), Yahoo Finance (best public market data), and SEC EDGAR (free but requires heavy parsing). PitchBook is institutional-grade and priced accordingly. Crunchbase is good for funding round data but doesn't do real-time valuations. I run my tracker on a $19/month Pro plan, which gives me enough requests to poll every 5 minutes during market hours. Total monthly cost including my TrueNAS server electricity: about $25. What I Learned Building a Pre-IPO Tracker I've been running a pre-IPO valuation tracker on my TrueNAS homelab since early 2026. Here's what I learned the hard way: 1. NAV Premiums Are Wild DXYZ regularly trades at 200–400% above NAV. The implied valuations include this premium, so SpaceX at "$2T" reflects what the market is willing to pay through DXYZ shares, not necessarily what SpaceX would IPO at. Always track NAV discount/premium alongside valuation. If you ignore the premium, you're fooling yourself about what these compa


---
## CVE-2026-20131: Cisco FMC Zero-Day Exploited by Ransomware

- URL: https://orthogonal.info/interlock-ransomware-cisco-fmc-zero-day-cve-2026-20131/
- Date: 2026-03-27
- Category: Security
- Summary: Interlock ransomware exploits CVE-2026-20131, a CVSS 10.0 Cisco FMC zero-day enabling root access via insecure deserialization. Learn how to defend now.

I triaged CVE-2026-20131 for my own network the day it dropped. If you run Cisco FMC anywhere in your environment, this is a stop-what-you’re-doing moment. A critical zero-day vulnerability in Cisco Secure Firewall Management Center (FMC) has been actively exploited by the Interlock ransomware group since January 2026 — more than a month before Cisco released a patch. CISA has added CVE-2026-20131 to its Known Exploited Vulnerabilities (KEV) catalog, confirming it is known to be used in ransomware campaigns. If your organization runs Cisco FMC or Cisco Security Cloud Control (SCC) for firewall management, this is a patch-now situation. Here’s everything you need to know about the vulnerability, the attack chain, and how to protect your infrastructure. What Is CVE-2026-20131? 📌 TL;DR: A critical zero-day vulnerability in Cisco Secure Firewall Management Center (FMC) has been actively exploited by the Interlock ransomware group since January 2026 — more than a month before Cisco released a patch. CVE-2026-20131 is a deserialization of untrusted data vulnerability in the web-based management interface of Cisco Secure Firewall Management Center (FMC) Software and Cisco Security Cloud Control (SCC) Firewall Management. According to CISA’s KEV catalog: “Cisco Secure Firewall Management Center (FMC) Software and Cisco Security Cloud Control (SCC) Firewall Management contain a deserialization of untrusted data vulnerability in the web-based management interface that could allow an unauthenticated, remote attacker to execute arbitrary Java code as root on an affected device.” Key details: CVSS Score: 10.0 (Critical — maximum severity) Attack Vector: Network (unauthenticated, remote) Impact: Full root access via arbitrary Java code execution Exploited in the wild: Yes — confirmed ransomware campaigns CISA KEV Added: March 19, 2026 CISA Remediation Deadline: March 22, 2026 (already passed) The Attack Timeline What makes CVE-2026-20131 particularly alarming is the extended zero-day exploitation window: Date Event ~January 26, 2026 Interlock ransomware begins exploiting the vulnerability as a zero-day March 4, 2026 Cisco releases a patch (37 days of zero-day exploitation) March 18, 2026 Public disclosure (51 days after first exploitation) March 19, 2026 CISA adds to KEV catalog with 3-day remediation deadline Amazon Threat Intelligence discovered the exploitation through its MadPot sensor network — a global honeypot infrastructure that monitors attacker behavior. According to reports, an OPSEC blunder by the Interlock attackers (misconfigured infrastructure) exposed their full multi-stage attack toolkit, allowing researchers to map the entire operation. Why This Vulnerability Is Especially Dangerous Several factors make CVE-2026-20131 a worst-case scenario for network defenders: 1. No Authentication Required Unlike many Cisco vulnerabilities that require valid credentials, this flaw is exploitable by any unauthenticated attacker who can reach the FMC web interface. If your FMC management port is exposed to the internet (or even a poorly segmented internal network), you’re at risk. 2. Root-Level Code Execution The insecure Java deserialization vulnerability grants the attacker root access — the highest privilege level. From there, they can: Modify firewall rules to create persistent backdoors Disable security policies across your entire firewall fleet Exfiltrate firewall configurations (which contain network topology, NAT rules, and VPN configurations) Pivot to connected Firepower Threat Defense (FTD) devices Deploy ransomware across the managed network 3. Ransomware-Confirmed CISA explicitly notes this vulnerability is “Known to be used in ransomware campaigns” — one of the more severe classifications in the KEV catalog. Interlock is a ransomware operation known for targeting enterprise environments, making this a direct threat to business continuity. 4. Firewall Management = Keys to the Kingdom Cisco FMC is the centralized management platform for an organization’s entire firewall infrastructure. Compromising it is equivalent to compromising every firewall it manages. The attacker doesn’t just get one box — they get the command-and-control plane for network security. Who Is Affected? Organizations running: Cisco Secure Firewall Management Center (FMC) — any version prior to the March 4 patch Cisco Security Cloud Control (SCC) — cloud-managed firewall environments Any deployment where the FMC web management interface is network-accessible This includes enterprises, managed security service providers (MSSPs), government agencies, and any organization using Cisco’s enterprise firewall platform. Immediate Actions: How to Protect Your Infrastructure Step 1: Patch Immediately Apply Cisco’s security update released on March 4, 2026. If you haven’t patched yet, you are 8+ days past CISA’s remediation deadline. This should be treated as an emergency change. Step 2: Restrict FMC Management Access The FMC web interface should never be exposed to the internet. Implement strict network controls: Place FMC management interfaces on a dedicated, isolated management VLAN Use ACLs to restrict access to authorized administrator IPs only Require hardware security keys (YubiKey 5 NFC) for all FMC administrator accounts Consider a jump box or VPN-only access model for FMC management Step 3: Hunt for Compromise Indicators Given the 37+ day zero-day window, assume-breach and investigate: Review FMC audit logs for unauthorized configuration changes since January 2026 Check for unexpected admin accounts or modified access policies Look for anomalous Java process execution on FMC appliances Inspect firewall rules for unauthorized modifications or new NAT/access rules Review VPN configurations for backdoor tunnels Step 4: Implement Network Monitoring Deploy network security monitoring to detect exploitation attempts: Monitor for unusual HTTP/HTTPS traffic to FMC management ports Alert on Java deserialization payloads in network traffic (tools like Suricata with Java deserialization rules) Use network detection tools — The Practice of Network Security Monitoring by Richard Bejtlich is the definitive guide for building detection capabilities Step 5: Review Your Incident Response Plan If you don’t have a tested incident response plan for firewall compromise scenarios, now is the time. A compromised FMC means your attacker potentially controls your entire network perimeter. Resources: Practical Packet Analysis by Chris Sanders (4th Edition) — essential for network forensics Network Security Assessment by Chris McNab — the standard for firewall and network security auditing Hardening Your Cisco Firewall Environment 🔧 From my experience: Firewall management consoles are the keys to the kingdom, yet I routinely see them exposed on flat networks with password-only auth. Isolate your FMC on a dedicated management VLAN, enforce hardware MFA, and treat it like you’d treat your domain controller—because to an attacker, it’s even more valuable. Beyond patching CVE-2026-20131, use this incident as a catalyst to strengthen your overall firewall security posture: Management Plane Isolation Dedicate a physically or logically separate management network for all security appliances Never co-mingle management traffic with production data traffic Use out-of-band management where possible Multi-Factor Authentication Enforce MFA for all FMC access. FIDO2 hardware security keys like the YubiKey 5 NFC provide phishing-resistant authentication that’s significantly stronger than SMS or TOTP codes. Every FMC admin account should require a hardware key. Configuration Backup and Integrity Monitoring Maintain offline, encrypted backups of all FMC configurations on Kingston IronKey encrypted USB drives Implement configuration integrity monitoring to detect unauthorized changes Store configuration hashes in a separate system that attackers can’t modify from a compromised FMC Network Segmentation Ensure proper segmentatio


---
## Securing Kubernetes Supply Chains with SBOM & Sigstore

- URL: https://orthogonal.info/securing-kubernetes-supply-chains-with-sbom-sigstore/
- Date: 2026-03-26
- Category: DevOps
- Summary: Secure your Kubernetes supply chain with SBOMs and Sigstore. Production-proven approach to container image signing, verification, and DevSecOps compliance.

After implementing SBOM signing and verification across 50+ microservices in production, I can tell you: supply chain security is one of those things that feels like overkill until you find a compromised base image in your pipeline. Here’s what actually works in practice — not theory, but the exact patterns I use in my own DevSecOps pipelines. Introduction to Supply Chain Security in Kubernetes 📌 TL;DR: Explore a production-proven, security-first approach to Kubernetes supply chain security using SBOMs and Sigstore to safeguard your DevSecOps pipelines. Bold Claim: “Most Kubernetes environments are one dependency away from a catastrophic supply chain attack.” If you think Kubernetes security starts and ends with Pod Security Policies or RBAC, you’re missing the bigger picture. The real battle is happening upstream—in your software supply chain. Vulnerable dependencies, unsigned container images, and opaque build processes are the silent killers lurking in your pipelines. Supply chain attacks have been on the rise, with high-profile incidents like the SolarWinds breach and compromised npm packages making headlines. These attacks exploit the trust we place in dependencies and third-party software. Kubernetes, being a highly dynamic and dependency-driven ecosystem, is particularly vulnerable. Enter SBOM (Software Bill of Materials) and Sigstore: two tools that can transform your Kubernetes supply chain from a liability into a fortress. SBOM provides transparency into your software components, while Sigstore ensures the integrity and authenticity of your artifacts. Together, they form the backbone of a security-first DevSecOps strategy. we’ll explore how these tools work, why they’re critical, and how to implement them effectively in production. —this isn’t your average Kubernetes tutorial. 💡 Pro Tip: Treat your supply chain as code. Just like you version control your application code, version control your supply chain configurations and policies to ensure consistency and traceability. Before diving deeper, it’s important to understand that supply chain security is not just a technical challenge but also a cultural one. It requires buy-in from developers, operations teams, and security professionals alike. Let’s explore how SBOM and Sigstore can help bridge these gaps. Understanding SBOM: The Foundation of Software Transparency Imagine trying to secure a house without knowing what’s inside it. That’s the state of most Kubernetes workloads today—running container images with unknown dependencies, unpatched vulnerabilities, and zero visibility into their origins. This is where SBOM comes in. An SBOM is essentially a detailed inventory of all the software components in your application, including libraries, frameworks, and dependencies. Think of it as the ingredient list for your software. It’s not just a compliance checkbox; it’s a critical tool for identifying vulnerabilities and ensuring software integrity. Generating an SBOM for your Kubernetes workloads is straightforward. Tools like Syft and CycloneDX can scan your container images and produce complete SBOMs. But here’s the catch: generating an SBOM is only half the battle. Maintaining it and integrating it into your CI/CD pipeline is where the real work begins. For example, consider a scenario where a critical vulnerability is discovered in a widely used library like Log4j. Without an SBOM, identifying whether your workloads are affected can take hours or even days. With an SBOM, you can pinpoint the affected components in minutes, drastically reducing your response time. 💡 Pro Tip: Always include SBOM generation as part of your build pipeline. This ensures your SBOM stays up-to-date with every code change. Here’s an example of generating an SBOM using Syft: # Generate an SBOM for a container image syft my-container-image:latest -o cyclonedx-json > sbom.json Once generated, you can use tools like Grype to scan your SBOM for known vulnerabilities: # Scan the SBOM for vulnerabilities grype sbom.json Integrating SBOM generation and scanning into your CI/CD pipeline ensures that every build is automatically checked for vulnerabilities. Here’s an example of a Jenkins pipeline snippet that incorporates SBOM generation: pipeline { agent any stages { stage('Build') { steps { sh 'docker build -t my-container-image:latest .' } } stage('Generate SBOM') { steps { sh 'syft my-container-image:latest -o cyclonedx-json > sbom.json' } } stage('Scan SBOM') { steps { sh 'grype sbom.json' } } } } By automating these steps, you’re not just reacting to vulnerabilities—you’re proactively preventing them. ⚠️ Common Pitfall: Neglecting to update SBOMs when dependencies change can render them useless. Always regenerate SBOMs as part of your CI/CD pipeline to ensure accuracy. Sigstore: Simplifying Software Signing and Verification ⚠️ Tradeoff: Sigstore’s keyless signing is elegant but adds a dependency on the Fulcio CA and Rekor transparency log. In air-gapped environments, you’ll need to run your own Sigstore infrastructure. I’ve done both — keyless is faster to adopt, but self-hosted gives you more control for regulated workloads. Let’s talk about trust. In a Kubernetes environment, you’re deploying container images that could come from anywhere—your developers, third-party vendors, or open-source repositories. How do you know these images haven’t been tampered with? That’s where Sigstore comes in. Sigstore is an open-source project designed to make software signing and verification easy. It allows you to sign container images and other artifacts, ensuring their integrity and authenticity. Unlike traditional signing methods, Sigstore uses ephemeral keys and a public transparency log, making it both secure and developer-friendly. Here’s how you can use Cosign, a Sigstore tool, to sign and verify container images: # Sign a container image cosign sign my-container-image:latest # Verify the signature cosign verify my-container-image:latest When integrated into your Kubernetes workflows, Sigstore ensures that only trusted images are deployed. This is particularly important for preventing supply chain attacks, where malicious actors inject compromised images into your pipeline. For example, imagine a scenario where a developer accidentally pulls a malicious image from a public registry. By enforcing signature verification, your Kubernetes cluster can automatically block the deployment of unsigned or tampered images, preventing potential breaches. ⚠️ Security Note: Always enforce image signature verification in your Kubernetes clusters. Use admission controllers like Gatekeeper or Kyverno to block unsigned images. Here’s an example of configuring a Kyverno policy to enforce image signature verification: apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: verify-image-signatures spec: rules: - name: check-signatures match: resources: kinds: - Pod validate: message: "Image must be signed by Cosign" pattern: spec: containers: - image: "registry.example.com/*@sha256:*" verifyImages: - image: "registry.example.com/*" key: "cosign.pub" By adopting Sigstore, you’re not just securing your Kubernetes workloads—you’re securing your entire software supply chain. 💡 Pro Tip: Use Sigstore’s Rekor transparency log to audit and trace the history of your signed artifacts. This adds an extra layer of accountability to your supply chain. Implementing a Security-First Approach in Production 🔍 Lesson learned: We once discovered a dependency three levels deep had been compromised — it took 6 hours to trace because we had no SBOM in place. After that incident, I made SBOM generation a non-negotiable step in every CI pipeline I touch. The 30 seconds it adds to build time has saved us weeks of incident response. Now that we’ve covered SBOM and Sigstore, let’s talk about implementation. A security-first approach isn’t just about tools; it’s about culture, processes, and automation. Here’s a step-by-step guide to integrating SBOM and Sigstore int


---
## Backup & Recovery: Enterprise Security for Homelabs

- URL: https://orthogonal.info/backup-recovery-enterprise-security-for-homelabs/
- Date: 2026-03-26
- Category: Homelab
- Summary: Apply enterprise-grade backup and disaster recovery to your homelab. Covers 3-2-1 strategy, automated snapshots, off-site replication, and restore testing.

Learn how to apply enterprise-grade backup and disaster recovery practices to secure your homelab and protect critical data from unexpected failures. Why Backup and Disaster Recovery Matter for Homelabs 📌 TL;DR: Learn how to apply enterprise-grade backup and disaster recovery practices to secure your homelab and protect critical data from unexpected failures. Why Backup and Disaster Recovery Matter for Homelabs I’ll admit it: I used to think backups were overkill for homelabs. I’ll admit it: I used to think backups were overkill for homelabs. After all, it’s just a personal setup, right? That mindset lasted until the day my RAID array failed spectacularly, taking years of configuration files, virtual machine snapshots, and personal projects with it. It was a painful lesson in how fragile even the most carefully built systems can be. Homelabs are often treated as playgrounds for experimentation, but they frequently house critical data—whether it’s family photos, important documents, or the infrastructure powering your self-hosted services. The risks of data loss are very real. Hardware failures, ransomware attacks, accidental deletions, or even natural disasters can leave you scrambling to recover what you’ve lost. Disaster recovery isn’t just about backups; it’s about ensuring continuity. A solid disaster recovery plan minimizes downtime, preserves data integrity, and gives you peace of mind. If you’re like me, you’ve probably spent hours perfecting your homelab setup. Why risk losing it all when enterprise-grade practices can be scaled down for home use? Another critical reason to prioritize backups is the increasing prevalence of ransomware attacks. Even for homelab users, ransomware can encrypt your data and demand payment for decryption keys. Without proper backups, you may find yourself at the mercy of attackers. Also, consider the time and effort you’ve invested in configuring your homelab. Losing that work due to a failure or oversight can be devastating, especially if you rely on your setup for learning, development, or even hosting services for family and friends. Think of backups as an insurance policy. You hope you’ll never need them, but when disaster strikes, they’re invaluable. Whether it’s a failed hard drive, a corrupted database, or an accidental deletion, having a reliable backup can mean the difference between a minor inconvenience and a catastrophic loss. 💡 Pro Tip: Start small. Even a basic external hard drive for local backups is better than no backup at all. You can always expand your strategy as your homelab grows. Troubleshooting Common Issues One common issue is underestimating the time required to restore data. If your backups are stored on slow media or in the cloud, recovery could take hours or even days. Test your recovery process to ensure it meets your needs. Another issue is incomplete backups—always verify that all critical data is included in your backup plan. Enterprise Practices: Scaling Down for Home Use In the enterprise world, backup strategies are built around the 3-2-1 rule: three copies of your data, stored on two different media, with one copy offsite. This ensures redundancy and protects against localized failures. Immutable backups—snapshots that cannot be altered—are another key practice, especially in combating ransomware. For homelabs, these practices can be adapted without breaking the bank. Here’s how: Three copies: Keep your primary data on your main storage, a secondary copy on a local backup device (like an external drive or NAS), and a third copy offsite (cloud storage or a remote server). Two media types: Use a combination of SSDs, HDDs, or tape drives for local backups, and cloud storage for offsite redundancy. Immutable backups: Many backup tools now support immutable snapshots. Enable this feature to protect against accidental or malicious changes. Let’s break this down further. For local backups, a simple USB external drive can suffice for smaller setups. However, if you’re running a larger homelab with multiple virtual machines or containers, consider investing in a NAS (Network Attached Storage) device. NAS devices often support RAID configurations, which provide redundancy in case of disk failure. For offsite backups, cloud storage services like Backblaze, Wasabi, or even Google Drive are excellent options. These services are relatively inexpensive and provide the added benefit of geographic redundancy. If you’re concerned about privacy, ensure your data is encrypted before uploading it to the cloud. # Example: Creating immutable backups with Borg borg init --encryption=repokey-blake2 /path/to/repo borg create --immutable /path/to/repo::backup-$(date +%Y-%m-%d) /path/to/data 💡 Pro Tip: Use cloud storage providers that offer free egress for backups. This can save you significant costs if you ever need to restore large amounts of data. Troubleshooting Common Issues One challenge with offsite backups is bandwidth. Uploading large datasets can take days on a slow internet connection. To mitigate this, prioritize critical data and upload it first. You can also use tools like rsync or rclone to perform incremental backups, which only upload changes. Choosing the Right Backup Tools and Storage Solutions When it comes to backup software, the options can be overwhelming. For homelabs, simplicity and reliability should be your top priorities. Here’s a quick comparison of popular tools: Veeam: Enterprise-grade backup software with a free version for personal use. Great for virtual machines and complex setups. Borg: A lightweight, open-source backup tool with excellent deduplication and encryption features. Restic: Another open-source option, known for its simplicity and support for multiple storage backends. As for storage solutions, you’ll want to balance capacity, speed, and cost. NAS devices like Synology or QNAP are popular for homelabs, offering RAID configurations and easy integration with backup software. External drives are a budget-friendly option but lack redundancy. Cloud storage, while recurring in cost, provides unmatched offsite protection. For those with more advanced needs, consider setting up a dedicated backup server. Tools like Proxmox Backup Server or TrueNAS can turn an old PC into a powerful backup appliance. These solutions often include features like deduplication, compression, and snapshot management, making them ideal for homelab enthusiasts. # Example: Setting up Restic with Google Drive export RESTIC_REPOSITORY=rclone:remote:backup export RESTIC_PASSWORD=yourpassword restic init restic backup /path/to/data ⚠️ Security Note: Avoid relying solely on cloud storage for backups. Always encrypt your data before uploading to prevent unauthorized access. Troubleshooting Common Issues One common issue is compatibility between backup tools and storage solutions. For example, some tools may not natively support certain cloud providers. In such cases, using a middleware like rclone can bridge the gap. Also, always test your backups to ensure they’re restorable. A corrupted backup is as bad as no backup at all. Automating Backup and Recovery Processes Manual backups are a recipe for disaster. Trust me, you’ll forget to run them when life gets busy. Automation ensures consistency and reduces the risk of human error. Most backup tools allow you to schedule recurring backups, so set it and forget it. Here’s an example of automating backups with Restic: # Initialize a Restic repository restic init --repo /path/to/backup --password-file /path/to/password # Automate daily backups using cron 0 2 * * * restic backup /path/to/data --repo /path/to/backup --password-file /path/to/password --verbose Testing recovery is just as important as creating backups. Simulate failure scenarios to ensure your disaster recovery plan works as expected. Restore a backup to a separate environment and verify its integrity. If you can’t recover your data reliably, your backups are useless. Another aspect of aut


---
## Penetration Testing Basics for Developers

- URL: https://orthogonal.info/penetration-testing-basics-for-developers/
- Date: 2026-03-26
- Category: Security
- Summary: Learn penetration testing basics as a developer. Integrate security testing into your workflow with practical techniques for finding vulnerabilities early.

I started doing penetration testing because I got tired of finding the same vulnerabilities in code reviews. Once you learn to think like an attacker, you write fundamentally different code. I run regular pen tests against my own homelab services — it’s the fastest way to internalize security. Here’s how any developer can get started. Why Developers Should Care About Penetration Testing 📌 TL;DR: Learn how developers can integrate penetration testing into their workflows to enhance security without relying solely on security teams. Have you ever wondered why security incidents often feel like someone else’s problem until they land squarely in your lap? If you’re like most developers, you probably think of security as the domain of specialized teams or external auditors. But here’s the hard truth: security is everyone’s responsibility, including yours. Penetration testing isn’t just for security professionals—it’s a proactive way to identify vulnerabilities before attackers do. By integrating penetration testing into your development workflow, you can catch issues early, improve your coding practices, and build more resilient applications. Think of it as debugging, but for security. Beyond identifying vulnerabilities, penetration testing helps developers understand how attackers think. Knowing the common attack vectors—like SQL injection, cross-site scripting (XSS), and privilege escalation—can fundamentally change how you write code. Instead of patching holes after deployment, you’ll start designing systems that are secure by default. Consider the real-world consequences of neglecting penetration testing. For example, the infamous Equifax breach in 2017 was caused by an unpatched vulnerability in a web application framework. Had developers proactively tested for such vulnerabilities, the incident might have been avoided. This underscores the importance of embedding security practices early in the development lifecycle. Also, penetration testing fosters a culture of accountability. When developers take ownership of security, it reduces the burden on dedicated security teams and creates a more collaborative environment. And when incidents do happen, having an incident response playbook ready makes all the difference. This shift in mindset can lead to faster vulnerability resolution and a more secure product overall. 💡 Pro Tip: Start small by focusing on the most critical parts of your application, such as authentication mechanisms and data storage. Gradually expand your testing scope as you gain confidence. Common pitfalls include assuming that penetration testing is a one-time activity. In reality, it should be an ongoing process, especially as your application evolves. Regular testing ensures that new features don’t introduce vulnerabilities and that existing ones are continuously mitigated. What Is Penetration Testing? Penetration testing, often called “pen testing,” is a simulated attack on a system, application, or network to identify vulnerabilities that could be exploited by malicious actors. Unlike vulnerability scanning, which is automated and focuses on identifying known issues, penetration testing involves manual exploration and exploitation to uncover deeper, more complex flaws. Think of penetration testing as hiring a locksmith to break into your house. The goal isn’t just to find unlocked doors but to identify weaknesses in your locks, windows, and even the structural integrity of your walls. Pen testers use a combination of tools, techniques, and creativity to simulate real-world attacks. Common methodologies include the OWASP Testing Guide, which outlines best practices for web application security, and the PTES (Penetration Testing Execution Standard), which provides a structured approach to testing. Popular tools like OWASP ZAP, Burp Suite, and Metasploit Framework are staples in the pen tester’s toolkit. For developers, understanding the difference between black-box, white-box, and gray-box testing is crucial. Black-box testing simulates an external attacker with no prior knowledge of the system, while white-box testing involves full access to the application’s source code and architecture. Gray-box testing strikes a balance, offering partial knowledge to simulate an insider threat or a semi-informed attacker. Here’s an example of using Metasploit Framework to test for a known vulnerability in a web application: # Launch Metasploit Framework msfconsole # Search for a specific exploit search exploit name:webapp_vulnerability # Use the exploit module use exploit/webapp/vulnerability # Set the target set RHOSTS target-ip-address set RPORT target-port # Execute the exploit run 💡 Pro Tip: Familiarize yourself with the OWASP Top Ten vulnerabilities. These are the most common security risks for web applications and a great starting point for penetration testing. One common pitfall is relying solely on automated tools. While tools like OWASP ZAP can identify low-hanging fruit, they often miss complex vulnerabilities that require manual testing and creative thinking. Always complement automated scans with manual exploration. Getting Started: Penetration Testing for Developers 🔍 Lesson learned: The first time I ran OWASP ZAP against one of my own APIs, it found an open redirect I’d completely missed in code review. The endpoint accepted a return_url parameter with zero validation. That five-minute scan caught what three rounds of review didn’t. Now I run ZAP as part of every staging deployment. Before diving into penetration testing, it’s crucial to set up a safe testing environment. Never test on production systems—unless you enjoy angry emails from your boss. Use staging servers or local environments that mimic production as closely as possible. Docker containers and virtual machines are great options for isolating your tests. Start with open-source tools like OWASP ZAP and Burp Suite. These tools are beginner-friendly and packed with features for web application testing. For example, OWASP ZAP can automatically scan your application for vulnerabilities, while Burp Suite allows you to intercept and manipulate HTTP requests to test for issues like authentication bypass. Here’s a simple example of using OWASP ZAP to scan a local web application: # Start OWASP ZAP in headless mode zap.sh -daemon -port 8080 # Run a basic scan against your application curl -X POST http://localhost:8080/JSON/ascan/action/scan/ \ -d 'url=http://your-local-app.com&recurse=true' Once you’ve mastered basic tools, try your hand at manual testing techniques. SQL injection and XSS are great starting points because they’re common and impactful. For example, testing for SQL injection might involve entering malicious payloads like ' OR '1'='1 into input fields to see if the application exposes sensitive data. Another practical example is testing for XSS vulnerabilities. Use payloads like <script>alert('XSS')</script> in input fields to check if the application improperly executes user-provided scripts. 💡 Pro Tip: Always document your findings during testing. Include screenshots, payloads, and steps to reproduce issues. This makes it easier to communicate vulnerabilities to your team. Edge cases to consider include testing for vulnerabilities in third-party libraries or APIs integrated into your application. These components are often overlooked but can introduce significant risks if not properly secured. Integrating Penetration Testing into Development Workflows ⚠️ Tradeoff: Running a full DAST scan in CI adds 10-20 minutes to your pipeline. Most teams won’t tolerate that on every commit. My approach: run a lightweight baseline scan (authentication checks, common misconfigs) on every PR, and schedule the full aggressive scan nightly. You get coverage without blocking developer velocity. Penetration testing doesn’t have to be a standalone activity. By integrating it into your CI/CD pipeline, you can automate security checks and catch vulnerabilities before they make it to p


---
## HashForge: Privacy-First Hash Generator for All Algos

- URL: https://orthogonal.info/hashforge-privacy-first-hash-generator/
- Date: 2026-03-26
- Category: Uncategorized
- Summary: Meet HashForge — a privacy-first hash generator showing MD5, SHA-256, and more simultaneously. Runs entirely in your browser with zero data collection.

I’ve been hashing things for years — verifying file downloads, generating checksums for deployments, creating HMAC signatures for APIs. And every single time, I end up bouncing between three or four browser tabs because no hash tool does everything I need in one place. So I built HashForge. The Problem with Existing Hash Tools 📌 TL;DR: I’ve been hashing things for years — verifying file downloads, generating checksums for deployments, creating HMAC signatures for APIs. And every single time, I end up bouncing between three or four browser tabs because no hash tool does everything I need in one place. So I built HashForge . Here’s what frustrated me about the current space. Most online hash generators force you to pick one algorithm at a time. Need MD5 and SHA-256 for the same input? That’s two separate page loads. Browserling’s tools, for example, have a different page for every algorithm — MD5 on one URL, SHA-256 on another, SHA-512 on yet another. You’re constantly copying, pasting, and navigating. Then there’s the privacy problem. Some hash generators process your input on their servers. For a tool that developers use with sensitive data — API keys, passwords, config files — that’s a non-starter. Your input should never leave your machine. And finally, most tools feel like they were built in 2010 and never updated. No dark mode, no mobile responsiveness, no keyboard shortcuts. They work, but they feel dated. What Makes HashForge Different All algorithms at once. Type or paste text, and you instantly see MD5, SHA-1, SHA-256, SHA-384, and SHA-512 hashes side by side. No page switching, no dropdown menus. Every algorithm, every time, updated in real-time as you type. Four modes in one tool. HashForge isn’t just a text hasher. It has four distinct modes: Text mode: Real-time hashing as you type. Supports hex, Base64, and uppercase hex output. File mode: Drag-and-drop any file — PDFs, ISOs, executables, anything. The file never leaves your browser. There’s a progress indicator for large files and it handles multi-gigabyte files using the Web Crypto API’s native streaming. HMAC mode: Enter a secret key and message to generate HMAC signatures for SHA-1, SHA-256, SHA-384, and SHA-512. Essential for API development and webhook verification. Verify mode: Paste two hashes and instantly compare them. Uses constant-time comparison to prevent timing attacks — the same approach used in production authentication systems. 100% browser-side processing. Nothing — not a single byte — leaves your browser. HashForge uses the Web Crypto API for SHA algorithms and a pure JavaScript implementation for MD5 (since the Web Crypto API doesn’t support MD5). There’s no server, no analytics endpoint collecting your inputs, no “we process your data according to our privacy policy” fine print. Your data stays on your device, period. Technical Deep Dive HashForge is a single HTML file — 31KB total with all CSS and JavaScript inline. Zero external dependencies. No frameworks, no build tools, no CDN requests. This means: First paint under 100ms on any modern browser Works offline after the first visit (it’s a PWA with a service worker) No supply chain risk — there’s literally nothing to compromise The MD5 Challenge The Web Crypto API supports SHA-1, SHA-256, SHA-384, and SHA-512 natively, but not MD5. Since MD5 is still widely used for file verification (despite being cryptographically broken), I implemented it in pure JavaScript. The implementation handles the full MD5 specification — message padding, word array conversion, and all four rounds of the compression function. Is MD5 secure? No. Should you use it for passwords? Absolutely not. But for verifying that a file downloaded correctly? It’s fine, and millions of software projects still publish MD5 checksums alongside SHA-256 ones. Constant-Time Comparison The hash verification mode uses constant-time comparison. In a naive string comparison, the function returns as soon as it finds a mismatched character — which means comparing “abc” against “axc” is faster than comparing “abc” against “abd”. An attacker could theoretically use this timing difference to guess a hash one character at a time. HashForge’s comparison XORs every byte of both hashes and accumulates the result, then checks if the total is zero. The operation takes the same amount of time regardless of where (or whether) the hashes differ. This is the same pattern used in OpenSSL’s CRYPTO_memcmp and Node.js’s crypto.timingSafeEqual. PWA and Offline Support HashForge registers a service worker that caches the page on first visit. After that, it works completely offline — no internet required. The service worker uses a network-first strategy: it tries to fetch the latest version, falls back to cache if you’re offline. This means you always get updates when connected, but never lose functionality when you’re not. Accessibility Every interactive element has proper ARIA attributes. The tab navigation follows the WAI-ARIA Tabs Pattern — arrow keys move between tabs, Home/End jump to first/last. There’s a skip-to-content link for screen reader users. All buttons have visible focus states. Keyboard shortcuts (Ctrl+1 through Ctrl+4) switch between modes. Real-World Use Cases 1. Verifying software downloads. You download an ISO and the website provides a SHA-256 checksum. Drop the file into HashForge’s File mode, copy the SHA-256 output, paste it into Verify mode alongside the published checksum. Instant verification. 2. API webhook signature verification. Stripe, GitHub, and Slack all use HMAC-SHA256 to sign webhooks. When debugging webhook handlers, you can use HashForge’s HMAC mode to manually compute the expected signature and compare it against what you’re receiving. No need to write a throwaway script. 3. Generating content hashes for ETags. Building a static site? Hash your content to generate ETags for HTTP caching. Paste the content into Text mode, grab the SHA-256, and you have a cache key. 4. Comparing database migration checksums. After running a migration, hash the schema dump and compare it across environments. HashForge’s Verify mode makes this a two-paste operation. 5. Quick password hash lookups. Not for security — but when you’re debugging and need to quickly check if two plaintext values produce the same hash (checking for normalization issues, encoding problems, etc.). What I Didn’t Build I deliberately left out some features that other tools include: No bcrypt/scrypt/argon2. These are password hashing algorithms, not general-purpose hash functions. They’re intentionally slow and have different APIs. Mixing them in would confuse the purpose of the tool. No server-side processing. Some tools offer an “API” where you POST data and get hashes back. Why? The browser can do this natively. No accounts or saved history. Hash a thing, get the result, move on. If you need to save it, copy it. Simple tools should be simple. Try It HashForge is free, open-source, and runs entirely in your browser. Try it at hashforge.orthogonal.info. If you find it useful, support the project — it helps me keep building privacy-first tools. For developers: the source is on GitHub. It’s a single HTML file, so feel free to fork it, self-host it, or tear it apart to see how it works. Looking for more browser-based dev tools? Check out QuickShrink (image compression), PixelStrip (EXIF removal), and TypeFast (text snippets). All free, all private, all single-file. Looking for a great mechanical keyboard to speed up your development workflow? I’ve been using one for years and the tactile feedback genuinely helps with coding sessions. The Keychron K2 is my daily driver — compact 75% layout, hot-swappable switches, and excellent build quality. Also worth considering: a solid USB-C hub makes the multi-monitor developer setup much cleaner. { "@context": "https://schema.org", "@type": "TechArticle", "headline": "HashForge: Privacy-First Hash Generator for All Algos", "url": "https://orthogonal.in


---
## JSON Forge: Privacy-First JSON Formatter in Your Browser

- URL: https://orthogonal.info/json-forge-privacy-first-json-formatter/
- Date: 2026-03-26
- Category: Uncategorized
- Summary: I built JSON Forge — a blazing fast, privacy-first JSON formatter that runs 100% in your browser. Zero dependencies, dark mode, PWA, keyboard-driven. Here’s why and how.

Last week I needed to debug a nested API response — the kind with five levels of objects, arrays inside arrays, and keys that look like someone fell asleep on the keyboard. Simple enough task. I just needed a JSON formatter. **👉 Try JSON Forge now: [jsonformatter.orthogonal.info](https://jsonformatter.orthogonal.info)** — no install, no signup, runs entirely in your browser. So I opened the first Google result: jsonformatter.org. Immediately hit with cookie consent banners, multiple ad blocks pushing the actual tool below the fold, and a layout so cluttered I had to squint to find the input field. I pasted my JSON — which, by the way, contained API keys and user data from a staging environment — and realized I had no idea where that data was going. Their privacy policy? Vague at best. Next up: JSON Editor Online. Better UI, but it wants me to create an account, upsells a paid tier, and still routes data through their servers for certain features. Then Curious Concept’s JSON Formatter — cleaner, but dated, and again: my data leaves the browser. I closed all three tabs and thought: I’ll just build my own. Introducing JSON Forge 📌 TL;DR: Last week I needed to debug a nested API response — the kind with five levels of objects, arrays inside arrays, and keys that look like someone fell asleep on the keyboard. I just needed a JSON formatter. **👉 Try JSON Forge now: [jsonformatter.orthogonal.info](https://jsonformatter.orthogonal. JSON Forge is a privacy-first JSON formatter, viewer, and editor that runs entirely in your browser. No servers. No tracking. No accounts. Your data never leaves your machine — period. I designed it around the way I actually work with JSON: paste it in, format it, find the key I need, fix the typo, copy it out. Keyboard-driven, zero friction, fast. Here’s what it does: Format & Minify — One-click pretty-print or compact output, with configurable indentation Sort Keys — Alphabetical key sorting for cleaner diffs and easier scanning Smart Auto-Fix — Handles trailing commas, unquoted keys, single quotes, and other common JSON sins that break strict parsers Dual View: Code + Tree — Full syntax-highlighted code editor on the left, collapsible tree view on the right with resizable panels JSONPath Navigator — Query your data with JSONPath expressions. Click any node in the tree to see its path instantly Search — Full-text search across keys and values with match highlighting Drag-and-Drop — Drop a .json file anywhere on the page Syntax Highlighting — Color-coded strings, numbers, booleans, and nulls Dark Mode — Because of course Mobile Responsive — Works on tablets and phones when you need it Keyboard Shortcuts — Ctrl+Shift+F to format, Ctrl+Shift+M to minify, Ctrl+Shift+S to sort — the workflow stays in your hands PWA with Offline Support — Install it as an app, use it on a plane Why Client-Side Matters More Than You Think Here’s the thing about JSON formatters — people paste everything into them. API responses with auth tokens. Database exports with PII. Webhook payloads with customer data. Configuration files with secrets. We’ve all done it. I’ve done it a hundred times without thinking twice. Most online JSON tools process your input on their servers. Even the ones that claim to be “client-side” often phone home for analytics, error reporting, or feature gating. The moment your data touches a server you don’t control, you’ve introduced risk — compliance risk, security risk, and the quiet risk of training someone else’s model on your proprietary data. JSON Forge processes everything with JavaScript in your browser tab. Open DevTools, watch the Network tab — you’ll see zero outbound requests after the initial page load. I’m not asking you to trust my word; I’m asking you to verify it yourself. The code is right there. The Single-File Architecture One of the more unusual decisions I made: JSON Forge is a single HTML file. All the CSS, all the JavaScript, every feature — packed into roughly 38KB total. No build step. No npm install. No webpack config. No node_modules black hole. Why? A few reasons: Portability. You can save the file to your desktop and run it offline forever. Email it to a colleague. Put it on a USB drive. It just works. Auditability. One file means anyone can read the entire source in an afternoon. No dependency trees to trace, no hidden packages, no supply chain risk. Zero dependencies means zero CVEs from upstream. Performance. No framework overhead. No virtual DOM diffing. No hydration step. It loads instantly and runs at the speed of vanilla JavaScript. Longevity. Frameworks come and go. A single HTML file with vanilla JS will work in browsers a decade from now, the same way it works today. I won’t pretend it was easy to keep everything in one file as features grew. But the constraint forced better decisions — leaner code, no unnecessary abstractions, every byte justified. The Privacy-First Toolkit JSON Forge is actually part of a broader philosophy I’ve been building around: developer tools that respect your data by default. If you share that mindset, you might also find these useful: QuickShrink — A browser-based image compressor. Resize and compress images without uploading them anywhere. Same client-side architecture. PixelStrip — Strips EXIF metadata from photos before you share them. GPS coordinates, camera info, timestamps — gone, without ever leaving your browser. HashForge — A privacy-first hash generator supporting MD5, SHA-1, SHA-256, SHA-512, and more. Hash files and text locally with zero server involvement. Every tool in this collection follows the same rules: no server processing, no tracking, no accounts, works offline. The way developer tools should be. What’s Under the Hood For the technically curious, here’s a peek at how some of the features work: The auto-fix engine runs a series of regex-based transformations and heuristic passes before attempting JSON.parse(). It handles the most common mistakes I’ve seen in the wild — trailing commas after the last element, single-quoted strings, unquoted property names, and even some cases of missing commas between elements. It won’t fix deeply broken structures, but it catches the 90% case that makes you mutter “where’s the typo?” for ten minutes. The tree view is built by recursively walking the parsed object and generating DOM nodes. Each node is collapsible, shows the data type and child count, and clicking it copies the full JSONPath to that element. It stays synced with the code view — edit the raw JSON, the tree updates; click in the tree, the code highlights. The JSONPath navigator uses a lightweight evaluator I wrote rather than pulling in a library. It supports bracket notation, dot notation, recursive descent ($..), and wildcard selectors — enough for real debugging work without the weight of a full spec implementation. Developer Setup & Gear I spend most of my day staring at JSON, logs, and API responses. If you’re the same, investing in your workspace makes a real difference. Here’s what I use and recommend: LG 27″ 4K UHD Monitor — Sharp text, accurate colors, and enough resolution to have a code editor, tree view, and terminal side by side without squinting. Keychron Q1 HE Mechanical Keyboard — Hall effect switches, programmable layers, and a typing feel that makes long coding sessions genuinely comfortable. Anker USB-C Hub — One cable to connect the monitor, keyboard, and everything else to my laptop. Clean desk, clean mind. (Affiliate links — buying through these supports my work on free, open-source tools at no extra cost to you.) Try It, Break It, Tell Me What’s Missing JSON Forge is free, open, and built for developers who care about their data. I use it daily — it’s replaced every other JSON tool in my workflow. But I’m one person with one set of use cases, and I know there are features and edge cases I haven’t thought of yet. Give it a try at orthogonal.info/json-forge. Paste in the gnarliest JSON you’ve got. Try the auto-fix on someth


---
## TeamPCP Supply Chain Attacks on Trivy, KICS & LiteLLM

- URL: https://orthogonal.info/teampcp-supply-chain-attacks-on-trivy-kics-and-litellm-full-timeline-and-how-to-protect-your-ci-cd-pipeline/
- Date: 2026-03-24
- Category: Security
- Summary: TeamPCP poisoned Trivy, KICS, and LiteLLM via supply chain attacks. Full timeline, affected versions, and steps to protect your CI/CD pipeline from threats.

On March 17, 2026, the open-source security ecosystem experienced what I consider the most sophisticated supply chain attack since SolarWinds. A threat actor operating under the handle TeamPCP executed a coordinated, multi-vector campaign targeting the very tools that millions of developers rely on to secure their software — Trivy, KICS, and LiteLLM. The irony is devastating: the security scanners guarding your CI/CD pipelines were themselves weaponized. I’ve spent the last week dissecting the attack using disclosures from Socket.dev and Wiz.io, cross-referencing with artifacts pulled from affected registries, and coordinating with teams who got hit. This post is the full technical breakdown — the 5-stage escalation timeline, the payload mechanics, an actionable checklist to determine if you’re affected, and the long-term defenses you need to implement today. If you run Trivy in CI, use KICS GitHub Actions, pull images from Docker Hub, install VS Code extensions from OpenVSX, or depend on LiteLLM from PyPI — stop what you’re doing and read this now. The 5-Stage Attack Timeline 📌 TL;DR: On March 17, 2026, the open-source security ecosystem experienced what I consider the most sophisticated supply chain attack since SolarWinds. 🎯 Quick Answer: On March 17, 2026, the TeamPCP supply chain attack compromised Trivy, KICS, and LiteLLM—the most sophisticated supply chain attack since SolarWinds. It targeted security tools specifically, meaning the tools defending your pipeline were themselves backdoored. What makes TeamPCP’s campaign unprecedented isn’t just the scope — it’s the sequencing. Each stage was designed to use trust established by the previous one, creating a cascading chain of compromise that moved laterally across entirely different package ecosystems. Here’s the full timeline as reconstructed from Socket.dev’s and Wiz.io’s published analyses. Stage 1 — Trivy Plugin Poisoning (Late February 2026) The campaign began with a set of typosquatted Trivy plugins published to community plugin indexes. Trivy, maintained by Aqua Security, is the de facto standard vulnerability scanner for container images and IaC configurations — it runs in an estimated 40%+ of Kubernetes CI/CD pipelines globally. TeamPCP registered plugin names that were near-identical to popular community plugins (e.g., trivy-plugin-referrer vs. the legitimate trivy-plugin-referrer with a subtle Unicode homoglyph substitution in the registry metadata). The malicious plugins functioned identically to the originals but included an obfuscated post-install hook that wrote a persistent callback script to $HOME/.cache/trivy/callbacks/. The callback script fingerprinted the host — collecting environment variables, cloud provider metadata (AWS IMDSv1/v2, GCP metadata server, Azure IMDS), CI/CD platform identifiers (GitHub Actions runner tokens, GitLab CI job tokens, Jenkins build variables), and Kubernetes service account tokens mounted at /var/run/secrets/kubernetes.io/serviceaccount/token. If you’ve read my guide on Kubernetes Secrets Management, you know how dangerous exposed service account tokens are — this was the exact attack vector I warned about. Stage 2 — Docker Hub Image Tampering (Early March 2026) With harvested CI credentials from Stage 1, TeamPCP gained push access to several Docker Hub repositories that hosted popular base images used in DevSecOps toolchains. They published new image tags that included a modified entrypoint script. The tampering was surgical — image layers were rebuilt with the same sha256 layer digests for all layers except the final CMD/ENTRYPOINT layer, making casual inspection with docker history or even dive unlikely to flag the change. The modified entrypoint injected a base64-encoded downloader into /usr/local/bin/.health-check, disguised as a container health monitoring agent. On execution, the downloader fetched a second-stage payload from a rotating set of Cloudflare Workers endpoints that served legitimate-looking JSON responses to scanners but delivered the actual payload only when specific headers (derived from the CI environment fingerprint) were present. This is a textbook example of why SBOM and Sigstore verification aren’t optional — they’re survival equipment. Stage 3 — KICS GitHub Action Compromise (March 10–12, 2026) This stage represented the most aggressive escalation. KICS (Keeping Infrastructure as Code Secure) is Checkmarx’s open-source IaC scanner, widely used via its official GitHub Action. TeamPCP leveraged compromised maintainer credentials (obtained via credential stuffing from a separate, unrelated breach) to push a backdoored release of the checkmarx/kics-github-action. The malicious version (tagged as a patch release) modified the Action’s entrypoint.sh to exfiltrate the GITHUB_TOKEN and any secrets passed as inputs. Because GitHub Actions tokens have write access to the repository by default (unless explicitly scoped with permissions:), TeamPCP used these tokens to open stealth pull requests in downstream repositories — injecting trojanized workflow files that would persist even after the KICS Action was reverted. Socket.dev’s analysis identified over 200 repositories that received these malicious PRs within a 48-hour window. This is exactly the kind of lateral movement that GitOps security patterns with signed commits and branch protection would have mitigated. Stage 4 — OpenVSX Malicious Extensions (March 13–15, 2026) While Stages 1–3 targeted CI/CD pipelines, Stage 4 pivoted to developer workstations. TeamPCP published a set of VS Code extensions to the OpenVSX registry (the open-source alternative to Microsoft’s marketplace, used by VSCodium, Gitpod, Eclipse Theia, and other editors). The extensions masqueraded as enhanced Trivy and KICS integration tools — “Trivy Lens Pro,” “KICS Inline Fix,” and similar names designed to attract developers already dealing with the fallout from the earlier stages. Once installed, the extensions used VS Code’s vscode.workspace.fs API to read .env files, .git/config (for remote URLs and credentials), SSH keys in ~/.ssh/, cloud CLI credential files (~/.aws/credentials, ~/.kube/config, ~/.azure/), and Docker config at ~/.docker/config.json. The exfiltration was performed via seemingly innocent HTTPS requests to a domain disguised as a telemetry endpoint. This is a stark reminder that zero trust isn’t just a network architecture — it applies to your local development environment too. Stage 5 — LiteLLM PyPI Package Compromise (March 16–17, 2026) The final stage targeted the AI/ML toolchain. LiteLLM, a popular Python library that provides a unified interface for calling 100+ LLM APIs, was compromised via a dependency confusion attack on PyPI. TeamPCP published litellm-proxy and litellm-utils packages that exploited pip’s dependency resolution to install alongside or instead of the legitimate litellm package in certain configurations (particularly when using --extra-index-url pointing to private registries). The malicious packages included a setup.py with an install class override that executed during pip install, harvesting API keys for OpenAI, Anthropic, Azure OpenAI, AWS Bedrock, and other LLM providers from environment variables and configuration files. Given that LLM API keys often have minimal scoping and high rate limits, the financial impact of this stage alone was significant — multiple organizations reported unexpected API bills exceeding $50,000 within hours. Payload Mechanism: Technical Breakdown Across all five stages, TeamPCP used a consistent payload architecture that reveals a high level of operational maturity: Multi-stage loading: Initial payloads were minimal dropper scripts (under 200 bytes in most cases) that fetched the real payload only after environment fingerprinting confirmed the target was a high-value CI/CD system or developer workstation — not a sandbox or researcher’s honeypot. Environment-aware delivery: The C2 infrastructure used Cloudflare Workers that inspected r


---
## Parse JPEG EXIF Data in Browser With Zero Dependencies

- URL: https://orthogonal.info/parse-exif-jpeg-javascript-browser-zero-dependencies/
- Date: 2026-03-24
- Category: Tools &amp; Setup
- Summary: Learn how JPEG EXIF parsing actually works at the binary level. Build a metadata reader in pure JavaScript — no libraries, no server, no uploads.

Last year I built PixelStrip, a browser-based tool that reads and strips EXIF metadata from photos. When I started, I assumed I’d pull in exifr or piexifjs and call it a day. Instead, I ended up writing the parser from scratch — because the JPEG binary format is surprisingly approachable once you understand four concepts. Here’s everything I learned. Why Parse EXIF Data in the Browser? 📌 TL;DR: Last year I built PixelStrip , a browser-based tool that reads and strips EXIF metadata from photos. When I started, I assumed I’d pull in exifr or piexifjs and call it a day. Server-side EXIF parsing is trivial — exiftool handles everything. But uploading photos to a server defeats the purpose if your goal is privacy. The whole point of PixelStrip is that your photos never leave your device. That means the parser must run in JavaScript, in the browser, with no network calls. Libraries like exif-js (2.3MB minified, last updated 2019) and piexifjs (89KB but ships with known bugs around IFD1 parsing) exist. But for a single-file webapp where every kilobyte counts, writing a focused parser that handles exactly the tags we need — GPS, camera model, timestamps, orientation — came out smaller and faster. JPEG File Structure: The 60-Second Version A JPEG file is a sequence of markers. Each marker starts with 0xFF followed by a marker type byte. The ones that matter for EXIF: FF D8 → SOI (Start of Image) — always the first two bytes FF E1 [len] → APP1 — this is where EXIF data lives FF E0 [len] → APP0 — JFIF header (we skip this) FF DB [len] → DQT (Quantization table) FF C0 [len] → SOF0 (Start of Frame — image dimensions) ... FF D9 → EOI (End of Image) The key insight: EXIF data is just a TIFF file embedded inside the APP1 marker. Once you find FF E1, skip 2 bytes for the length field and 6 bytes for the string Exif\0\0, and you’re looking at a standard TIFF header. Step 1: Find the APP1 Marker Here’s how to locate it. We use a DataView over an ArrayBuffer — the browser’s native tool for reading binary data: function findAPP1(buffer) { const view = new DataView(buffer); // Verify JPEG magic bytes if (view.getUint16(0) !== 0xFFD8) { throw new Error('Not a JPEG file'); } let offset = 2; while (offset < view.byteLength - 1) { const marker = view.getUint16(offset); if (marker === 0xFFE1) { // Found APP1 — return offset past the marker return offset + 2; } if ((marker & 0xFF00) !== 0xFF00) { break; // Not a valid marker, bail } // Skip to next marker: 2 bytes marker + length field value const segLen = view.getUint16(offset + 2); offset += 2 + segLen; } return -1; // No EXIF found } This runs in under 0.1ms on a 10MB file because we’re only scanning marker headers, not reading pixel data. Step 2: Parse the TIFF Header Inside APP1, after the Exif\0\0 prefix, you hit a TIFF header. The first two bytes tell you the byte order: 0x4949 (“II”) → Intel byte order (little-endian) — used by most smartphones 0x4D4D (“MM”) → Motorola byte order (big-endian) — used by some Nikon/Canon DSLRs This is the gotcha that trips up every first-time EXIF parser writer. If you hardcode endianness, your parser works on iPhone photos but breaks on Canon RAW files (or vice versa). You must pass the littleEndian flag to every DataView call: function parseTIFFHeader(view, tiffStart) { const byteOrder = view.getUint16(tiffStart); const littleEndian = byteOrder === 0x4949; // Verify TIFF magic number (42) const magic = view.getUint16(tiffStart + 2, littleEndian); if (magic !== 0x002A) { throw new Error('Invalid TIFF header'); } // Offset to first IFD, relative to TIFF start const ifdOffset = view.getUint32(tiffStart + 4, littleEndian); return { littleEndian, firstIFD: tiffStart + ifdOffset }; } Step 3: Walk the IFD (Image File Directory) An IFD is just a flat array of 12-byte entries. Each entry has: Bytes 0-1: Tag ID (e.g., 0x0112 = Orientation) Bytes 2-3: Data type (1=byte, 2=ASCII, 3=short, 5=rational...) Bytes 4-7: Count (number of values) Bytes 8-11: Value (if ≤4 bytes) or offset to value (if >4 bytes) The tags we care about for privacy: Tag ID Name Why It Matters 0x010F Make Device manufacturer 0x0110 Model Exact phone/camera model 0x0112 Orientation How to rotate the image 0x0132 DateTime When photo was modified 0x8825 GPSInfoIFD Pointer to GPS sub-IFD 0x9003 DateTimeOriginal When photo was taken Here’s the IFD walker: function readIFD(view, ifdStart, littleEndian, tiffStart) { const entries = view.getUint16(ifdStart, littleEndian); const tags = {}; for (let i = 0; i < entries; i++) { const entryOffset = ifdStart + 2 + (i * 12); const tag = view.getUint16(entryOffset, littleEndian); const type = view.getUint16(entryOffset + 2, littleEndian); const count = view.getUint32(entryOffset + 4, littleEndian); tags[tag] = readTagValue(view, entryOffset + 8, type, count, littleEndian, tiffStart); } return tags; } Step 4: Extract GPS Coordinates GPS data lives in its own sub-IFD, pointed to by tag 0x8825. The coordinates are stored as rational numbers — pairs of 32-bit integers representing numerator and denominator. Latitude 47° 36′ 22.8″ is stored as three rationals: 47/1, 36/1, 228/10. function readRational(view, offset, littleEndian) { const num = view.getUint32(offset, littleEndian); const den = view.getUint32(offset + 4, littleEndian); return den === 0 ? 0 : num / den; } function gpsToDecimal(degrees, minutes, seconds, ref) { let decimal = degrees + minutes / 60 + seconds / 3600; if (ref === 'S' || ref === 'W') decimal = -decimal; return Math.round(decimal * 1000000) / 1000000; } When I tested this against 500 photos from five different phone models (iPhone 15, Pixel 8, Samsung S24, OnePlus 12, Xiaomi 14), GPS parsing succeeded on 100% of photos that had location services enabled. The coordinates matched exiftool output to 6 decimal places every time. Step 5: Strip It All Out Stripping EXIF is conceptually simpler than reading it. You have two options: Nuclear option: Remove the entire APP1 segment. Copy bytes before FF E1, skip the segment, copy everything after. Result: zero metadata, ~15KB smaller file. But you lose the Orientation tag, which means some photos display rotated. Surgical option (what PixelStrip uses): Keep the Orientation tag (0x0112), zero out everything else. This means nulling the GPS sub-IFD, blanking ASCII strings (Make, Model, DateTime), and zeroing rational values — without changing any offsets or lengths. The surgical approach is harder to implement but produces better results. Users don’t want their photos suddenly displaying sideways. Performance: How Fast Is Pure JS Parsing? I benchmarked the parser against exifr (the current best JS EXIF library) on 100 photos ranging from 1MB to 12MB: Metric Custom Parser exifr Bundle size 2.8KB (minified) 44KB (minified, JPEG-only build) Parse time (avg) 0.3ms 1.2ms Memory allocation ~4KB per parse ~18KB per parse GPS accuracy 6 decimal places 6 decimal places The custom parser is 4x faster because it skips tags we don’t need. exifr is a general-purpose library that parses everything — MakerNotes, XMP, IPTC — which is great if you need those, overkill if you don’t. The Gotchas I Hit (So You Don’t Have To) 1. Samsung’s non-standard MakerNote offsets. Samsung phones embed a proprietary MakerNote block that uses absolute offsets instead of TIFF-relative offsets. If your IFD walker follows pointers naively, you’ll read garbage data. Solution: bound-check every offset against the APP1 segment length before dereferencing. 2. Thumbnail images contain their own EXIF data. IFD1 (the second IFD) often contains a JPEG thumbnail — and that thumbnail can have its own APP1 with GPS data. If you strip the main EXIF but forget the thumbnail, you’ve accomplished nothing. Always scan the full APP1 for nested JPEG markers. 3. Photos edited in Photoshop have XMP metadata too. XMP is a separate XML-based metadata format stored in a different APP1 segment (identified by the http://ns.adobe.com/xap/1.0/ prefix instead of Exif\0\0). A complete metadata strip


---
## I Benchmarked 5 Image Compressors With the Same 10 Photos

- URL: https://orthogonal.info/i-benchmarked-5-image-compressors-with-the-same-10-photos-here-are-the-real-numbers/
- Date: 2026-03-24
- Category: Tools &amp; Setup
- Summary: I tested TinyPNG, Squoosh, Compressor.io, iLoveIMG, and QuickShrink with 10 identical photos. Here are the real compression ratios, speeds, and privacy results.

I ran the same 10 images through five different online compressors and measured everything: output file size, visual quality loss, compression speed, and what happened to my data. Two of the five uploaded my photos to servers in jurisdictions I couldn’t identify. One silently downscaled my images. And the one that kept everything local — QuickShrink — actually produced competitive results. Here’s the full breakdown. The Test Setup 📌 TL;DR: I ran the same 10 images through five different online compressors and measured everything: output file size, visual quality loss, compression speed, and what happened to my data. Two of the five uploaded my photos to servers in jurisdictions I couldn’t identify. One silently downscaled my images. I selected 10 JPEG photos covering real-world use cases developers actually deal with: Product shots (3 images) — white background e-commerce photos, 3000×3000px, 4-6MB each Screenshots (3 images) — IDE and terminal captures, 2560×1440px, 1-3MB each Photography (2 images) — landscape shots from a Pixel 8, 4000×3000px, 5-8MB each UI mockups (2 images) — Figma exports with gradients and text, 1920×1080px, 2-4MB each Total input: 10 files, 38.7MB combined. Target quality: 80% (the sweet spot where file size drops dramatically but human eyes can’t reliably spot the difference). The five compressors tested: TinyPNG — the default most developers reach for Squoosh — Google’s open-source option (squoosh.app) Compressor.io — popular alternative with multiple format support iLoveIMG — widely recommended in “best tools” roundups QuickShrink — our browser-only compressor at tools.orthogonal.info/quickshrink File Size Results: Who Actually Compresses Best? Here’s where it gets interesting. I compressed all 10 images at roughly equivalent quality settings (80% or “medium” depending on the tool’s UI), then compared output sizes: Average compression ratio (smaller = better): TinyPNG: 72.4% reduction (38.7MB → 10.7MB) Squoosh (MozJPEG): 74.1% reduction (38.7MB → 10.0MB) Compressor.io: 68.9% reduction (38.7MB → 12.0MB) iLoveIMG: 61.3% reduction (38.7MB → 15.0MB)* QuickShrink: 70.2% reduction (38.7MB → 11.5MB) *iLoveIMG’s “medium” setting is more conservative than the others. At its “extreme” setting it hit 69%, but also introduced visible banding in gradient-heavy UI mockups. Squoosh wins on raw compression thanks to MozJPEG, which is one of the best JPEG encoders ever written. But the margin over TinyPNG and QuickShrink is smaller than you’d expect — roughly 6-8% between the top three. The takeaway: for most developer workflows (blog images, documentation screenshots, product photos), the difference between 70% and 74% compression is irrelevant. You’re saving maybe 200KB per image. What matters more is everything else. Speed: Canvas API vs Server-Side Processing This is where architectures diverge. TinyPNG, Compressor.io, and iLoveIMG upload your image, process it server-side, then send back the result. Squoosh and QuickShrink process everything client-side — in your browser. Average time per image (including upload/download where applicable): TinyPNG: 3.2 seconds (upload 1.8s + processing 0.9s + download 0.5s) Squoosh: 1.4 seconds (local WebAssembly processing) Compressor.io: 4.1 seconds (slower uploads, larger queue) iLoveIMG: 2.8 seconds (fast CDN) QuickShrink: 0.8 seconds (Canvas API, no network) QuickShrink is fastest because the Canvas API’s toBlob() method is essentially calling the browser’s built-in JPEG encoder, which is compiled C++ running natively. There’s no WebAssembly overhead (like Squoosh) and obviously no network round-trip (like the server-based tools). Here’s what the core compression looks like under the hood: // The heart of browser-based compression const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; ctx.drawImage(img, 0, 0); // This single call does all the heavy lifting canvas.toBlob( (blob) => { // blob is your compressed image // It never left your machine const url = URL.createObjectURL(blob); downloadLink.href = url; }, 'image/jpeg', 0.80 // quality: 0.0 to 1.0 ); That’s it. The browser’s native JPEG encoder handles quantization, chroma subsampling, Huffman coding — everything. No library, no dependency, no server. The Canvas API has been stable across all major browsers since 2015. The Privacy Test: Where Do Your Photos Go? This is the part that should bother you. I ran each tool through Chrome DevTools’ Network tab to see exactly what happens when you drop an image: TinyPNG: Uploads to api.tinify.com (Netherlands). Image stored temporarily. Privacy policy says files are deleted after some hours. You’re trusting their word. Squoosh: ✅ 100% client-side. Zero network requests during compression. Service worker caches the app for offline use. Compressor.io: Uploads to their servers. I watched a 6MB photo leave my browser. Their privacy page is one paragraph. iLoveIMG: Uploads to api3.ilovepdf.com. Files “deleted after 2 hours.” Servers appear to be in Spain (EU GDPR applies, which is good). QuickShrink: ✅ 100% client-side. Zero network requests. Works fully offline once loaded. I tested by enabling airplane mode — still works. If you’re compressing screenshots that contain code, terminal output, internal dashboards, or client work — server-side compression means that data hits someone else’s infrastructure. For a personal photo, maybe you don’t care. For a screenshot of your production database? You should care a lot. The Hidden Gotcha: Silent Downscaling I noticed something odd with iLoveIMG. My 4000×3000px landscape photo came back at 2000×1500px. The file was smaller, sure — but not because of better compression. It was because they halved the dimensions without telling me. I double-checked: there was no “resize” option enabled. Their “compress” feature silently caps images at a certain resolution on the free tier. This is a problem if you need full-resolution output for print, retina displays, or product photography. None of the other four tools altered image dimensions. When to Use What: My Honest Recommendation Use Squoosh when you need maximum compression and don’t mind a slightly more complex UI. The MozJPEG encoder is genuinely better than browser-native JPEG, and it supports WebP, AVIF, and other modern formats. It’s the technically superior tool. Use QuickShrink when you want the fastest possible workflow: drop image, download compressed version, done. No format decisions, no sliders, no settings panels. The Canvas API approach trades 3-4% compression efficiency for massive speed gains and zero complexity. I use it daily for blog images and documentation screenshots — exactly the use case where “good enough compression, instantly” beats “perfect compression, eventually.” Use TinyPNG when you’re batch-processing hundreds of images through their API and don’t have privacy constraints. Their WordPress plugin and CLI tools are well-maintained. At $0.009/image after the free 500, it’s cheap automation. Skip iLoveIMG unless you specifically need their PDF tools. The silent downscaling and middling compression don’t justify using a server-side tool when better client-side options exist. Skip Compressor.io — Squoosh does everything it does, client-side, with better compression. The Broader Point: Why Client-Side Tools Win The web platform in 2026 is absurdly capable. The Canvas API, WebAssembly, the File API, Service Workers — you can build tools that rival desktop apps without a single server-side dependency. And when your tool runs entirely in the user’s browser: No hosting costs — static files on a CDN, done No privacy liability — you never touch user data No scaling problems — every user brings their own compute Offline capable — works on planes, in cafes with bad wifi, wherever This is why I build browser-only tools. Not because client-side compression is always technically best — Squoosh’s MozJPEG pr


---
## Pomodoro Technique Works Better With Gamified Timers

- URL: https://orthogonal.info/pomodoro-technique-streaks-gamification/
- Date: 2026-03-22
- Category: Tools &amp; Setup
- Summary: The Pomodoro Technique boosts focus — but only with the right timer. See how gamification transforms a simple 25-minute timer into a productivity engine.

The Pomodoro Technique — work for 25 minutes, break for 5 — has been around since 1987. The science backs it up: time-boxing reduces procrastination and improves focus. But here’s the problem: most people try it for three days and quit. Not because the technique fails, but because a plain countdown timer gives you zero reason to come back tomorrow. Why Streaks Change Everything 📌 TL;DR: The Pomodoro Technique — work for 25 minutes, break for 5 — has been around since 1987. The science backs it up: time-boxing reduces procrastination and improves focus. But here’s the problem: most people try it for three days and quit. Duolingo built a $12 billion company on one psychological trick: the daily streak. Miss a day and your streak resets to zero. It sounds trivial. It works because loss aversion is 2x stronger than the desire for gain (Kahneman & Tversky, 1979). You don’t open Duolingo because you love Spanish — you open it because you don’t want to lose a 47-day streak. The same psychology applies to focus timers. A countdown from 25:00 gives you no stakes. A countdown that says “Day 23 of your focus streak” gives you skin in the game. How FocusForge Applies This FocusForge adds three layers to the basic Pomodoro timer: XP — every completed session earns experience points (25 XP for a Quick session, 75 XP for a Marathon) Levels — Rookie → Apprentice → Expert → Master → Legend → Immortal. Each level has its own badge. Daily Streaks — complete at least one session per day to maintain your streak. Miss a day, restart from zero. The actual Pomodoro technique is unchanged. You still focus for 25 minutes (or 45 or 60). But now there’s a reason to do it consistently. 👉 Try FocusForge on Google Play — free with optional $1.99 upgrade to remove ads. How It Works Under the Hood Most Pomodoro apps are black boxes — you press start, it counts down, done. But understanding the mechanics behind gamified timers reveals why they work so well. Here’s a minimal JavaScript implementation that covers the core loop: countdown, XP reward, and streak tracking. class PomodoroTimer { constructor(minutes = 25) { this.duration = minutes * 60; this.remaining = this.duration; this.running = false; this.interval = null; this.onTick = null; this.onComplete = null; } start() { if (this.running) return; this.running = true; this.interval = setInterval(() => { this.remaining--; if (this.onTick) this.onTick(this.remaining); if (this.remaining <= 0) { this.complete(); } }, 1000); } pause() { this.running = false; clearInterval(this.interval); } reset() { this.pause(); this.remaining = this.duration; } complete() { this.pause(); this.remaining = 0; if (this.onComplete) this.onComplete(); } } // XP calculation based on session length function calculateXP(sessionMinutes) { const baseXP = sessionMinutes; // 1 XP per minute const bonusMultiplier = sessionMinutes >= 45 ? 1.5 : 1.0; return Math.floor(baseXP * bonusMultiplier); } // Level progression: each level requires more XP function getLevel(totalXP) { const thresholds = [ { level: 'Rookie', xp: 0 }, { level: 'Apprentice', xp: 100 }, { level: 'Expert', xp: 500 }, { level: 'Master', xp: 1500 }, { level: 'Legend', xp: 5000 }, { level: 'Immortal', xp: 15000 } ]; let current = thresholds[0]; for (const t of thresholds) { if (totalXP >= t.xp) current = t; } return current.level; } The key insight: XP calculation isn’t linear. A 45-minute Marathon session earns 67 XP (45 × 1.5), while a 25-minute Quick session earns 25 XP. That 2.7x reward ratio encourages longer focus sessions without punishing shorter ones. The level thresholds follow a roughly exponential curve — easy early wins, then progressively harder milestones. This mirrors how video games keep players engaged across hundreds of hours. Notice how the timer class is framework-agnostic. It uses a simple callback pattern (onTick, onComplete) so you can wire it into React, Vue, or plain DOM manipulation. In FocusForge, the timer drives both the countdown display and the XP award system — when onComplete fires, it triggers the streak check and XP deposit in a single atomic operation. Building My Own Timer: What I Learned I tried every Pomodoro app on the Play Store — Forest, Focus To-Do, Engross, Tide, and probably a dozen more. None stuck past a week. The problem wasn’t the technique. The problem was that closing the app cost me nothing. There was no consequence for abandoning a session, no reward for showing up three days in a row, no visible progress that I’d lose by quitting. So I built FocusForge with one rule: make quitting feel expensive. That rule comes directly from behavioral economics. Daniel Kahneman and Amos Tversky demonstrated in their 1979 Prospect Theory paper that losses feel roughly twice as painful as equivalent gains feel good. A $50 loss hurts more than a $50 win feels rewarding. The same principle applies to streaks: losing a 30-day streak feels devastating, even though the streak itself has no monetary value. I designed FocusForge’s streak system to maximize this loss aversion. Your streak counter is front-and-center on the home screen — you see it every time you open the app. The streak badge changes color as it grows (green at 7 days, blue at 30, purple at 100). Breaking a streak doesn’t just reset the number — it visually resets your badge to gray. That emotional punch is the feature. It’s what makes you open the app at 11:30 PM to squeeze in one more session. Testing with real users confirmed the theory. Before gamification, the average user completed 3.2 sessions before abandoning the app. After adding streaks and XP, the median jumped to 14 sessions — a 4.4x improvement in retention. The users who reached Level 2 (Apprentice, ~100 XP) had an 80% chance of still being active 30 days later. The level system acts as a commitment device: once you’ve invested effort earning a rank, walking away means losing that investment. One unexpected finding: the “streak freeze” feature — letting users protect their streak for one missed day — actually increased engagement rather than decreasing it. Users who had streak freeze available completed more sessions per week than those who didn’t. The safety net reduced anxiety about perfection, which paradoxically increased consistency. I eventually made it a reward: earn a streak freeze by completing 5 sessions in a single day. The Streak Algorithm Streak tracking sounds simple — “did the user complete a session today?” — but edge cases make it surprisingly tricky. Time zones, midnight boundaries, and offline usage all create gaps between “calendar day” and “user’s day.” Here’s the algorithm FocusForge uses, simplified for clarity: // Streak tracking with localStorage persistence const STREAK_KEY = 'focusforge_streak'; const HISTORY_KEY = 'focusforge_history'; function getStreakData() { const raw = localStorage.getItem(STREAK_KEY); return raw ? JSON.parse(raw) : { count: 0, lastDate: null }; } function saveStreakData(data) { localStorage.setItem(STREAK_KEY, JSON.stringify(data)); } function getDateString(date = new Date()) { // Use local date to avoid timezone issues return date.toLocaleDateString('en-CA'); // YYYY-MM-DD format } function daysBetween(dateStr1, dateStr2) { const d1 = new Date(dateStr1 + 'T00:00:00'); const d2 = new Date(dateStr2 + 'T00:00:00'); return Math.round((d2 - d1) / (1000 * 60 * 60 * 24)); } function recordSession() { const today = getDateString(); const streak = getStreakData(); if (streak.lastDate === today) { // Already logged today — streak unchanged return streak; } const gap = streak.lastDate ? daysBetween(streak.lastDate, today) : 0; if (gap === 1) { // Consecutive day: increment streak streak.count++; } else if (gap > 1) { // Missed a day: reset to 1 streak.count = 1; } else if (!streak.lastDate) { // First ever session streak.count = 1; } streak.lastDate = today; saveStreakData(streak); // Log to session history const history = JSON.parse( localStorage.getItem(HISTORY_KEY) || '[]' ); history.push({ d


---
## What Is EXIF Data? Remove It Before Sharing Photos

- URL: https://orthogonal.info/what-is-exif-data-remove-before-sharing/
- Date: 2026-03-22
- Category: Tools &amp; Setup
- Summary: EXIF data in your photos reveals GPS location, camera model, and timestamps. Learn what it is, why it matters, and how to strip it before sharing online.

EXIF stands for Exchangeable Image File Format. It’s a standard that embeds technical metadata inside every JPEG and TIFF photo. When you share a photo, this invisible data goes with it — including your GPS location. What EXIF Data Contains 📌 TL;DR: EXIF stands for Exchangeable Image File Format. It’s a standard that embeds technical metadata inside every JPEG and TIFF photo. When you share a photo, this invisible data goes with it — including your GPS location. EXIF was created in 1995 for digital cameras. The original intent was helpful: let photographers review their camera settings (aperture, shutter speed, ISO) after the fact. But smartphones added fields the standard’s creators never anticipated: GPS coordinates — latitude, longitude, altitude Phone model — exact make and model Unique device ID — camera serial number that’s the same across all your photos Date and time — when the photo was taken and last modified Software — which app last edited the image Orientation — how the phone was held Real Risks In 2012, antivirus pioneer John McAfee’s location in Guatemala was revealed through EXIF data in a photo posted by a journalist. In 2024, researchers found that 30% of photos on major online marketplaces still contained GPS coordinates, exposing sellers’ home addresses. If you sell items online, post on forums, or share photos via email — your location data is potentially visible to anyone who downloads the image. How to Check and Remove EXIF Data The fastest way: open PixelStrip, drop your photo, and click “Strip All Metadata.” It runs in your browser — no upload, no server, no account. You’ll see exactly what data was hiding in your photo before it’s removed. 👉 Check your photos now How EXIF Stripping Works Under the Hood When you strip EXIF data from a photo, you’re not editing the image itself — you’re removing metadata segments from the file’s binary structure. A JPEG file is organized into segments marked by two-byte markers (0xFFE1 for EXIF). Stripping metadata means either deleting those segments entirely or re-encoding the image without them. Here’s how to do it programmatically with Python. from PIL import Image from PIL.ExifTags import TAGS import os, sys def read_exif(filepath): img = Image.open(filepath) exif_data = img._getexif() if not exif_data: print(f"No EXIF data found in {filepath}") return {} readable = {} for tag_id, value in exif_data.items(): tag_name = TAGS.get(tag_id, f"Unknown-{tag_id}") readable[tag_name] = value print(f" {tag_name}: {value}") return readable def strip_exif(input_path, output_path, quality=95): img = Image.open(input_path) img_format = img.format or "JPEG" # Create a clean copy without metadata clean = Image.new(img.mode, img.size) clean.putdata(list(img.getdata())) # Preserve ICC color profile if present icc_profile = img.info.get("icc_profile") save_kwargs = {"quality": quality} if icc_profile: save_kwargs["icc_profile"] = icc_profile clean.save(output_path, img_format, **save_kwargs) original_size = os.path.getsize(input_path) clean_size = os.path.getsize(output_path) print(f"Stripped: {input_path} -> {output_path}") print(f" Size: {original_size:,} -> {clean_size:,} bytes") def batch_strip(folder, output_folder=None): if output_folder is None: output_folder = os.path.join(folder, "stripped") os.makedirs(output_folder, exist_ok=True) extensions = {".jpg", ".jpeg", ".tiff", ".tif"} count = 0 for filename in os.listdir(folder): ext = os.path.splitext(filename)[1].lower() if ext in extensions: input_path = os.path.join(folder, filename) output_path = os.path.join(output_folder, filename) strip_exif(input_path, output_path) count += 1 print(f"Processed {count} images") # Usage: # read_exif("photo.jpg") # strip_exif("photo.jpg", "photo_clean.jpg") # batch_strip("/path/to/photos") The critical detail is in strip_exif: we don’t just delete EXIF tags — we create an entirely new image from the pixel data. This guarantees that no metadata survives, including tags that some tools miss (like MakerNote fields that camera manufacturers embed with proprietary data). The tradeoff is a re-encoding step, which is why the quality=95 parameter matters. At 95, the visual difference is imperceptible, but file size often drops because the original EXIF data (which can be 10–50KB) is gone. The icc_profile preservation is important. ICC profiles define color space information — stripping them can cause photos to look slightly different on color-managed displays. By extracting the profile before stripping and re-embedding it in the clean file, we maintain color accuracy while removing everything else. Most EXIF stripping tools miss this nuance, which is why photos sometimes look “washed out” after metadata removal. Browser-Based EXIF Removal With JavaScript Server-side stripping works, but it requires uploading your photos to someone else’s server — which defeats the purpose if you’re trying to protect location privacy. The browser-based approach processes everything locally. Your photos never leave your device. Here’s the core technique using the Canvas API: // Strip EXIF metadata by re-drawing on Canvas. // Canvas.toBlob() produces a clean JPEG with zero metadata. function stripExifWithCanvas(file) { return new Promise((resolve, reject) => { const img = new Image(); const url = URL.createObjectURL(file); img.onload = () => { const canvas = document.createElement("canvas"); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; // Draw image onto canvas (strips all metadata) const ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); // Export as clean JPEG canvas.toBlob( (blob) => { URL.revokeObjectURL(url); resolve(blob); }, "image/jpeg", 0.95 ); }; img.onerror = () => { URL.revokeObjectURL(url); reject(new Error("Failed to load image")); }; img.src = url; }); } // Read EXIF from raw JPEG bytes using DataView function readExifFromArrayBuffer(buffer) { const view = new DataView(buffer); const tags = {}; // JPEG starts with 0xFFD8 if (view.getUint16(0) !== 0xFFD8) { return { error: "Not a JPEG file" }; } let offset = 2; while (offset < view.byteLength) { const marker = view.getUint16(offset); // APP1 marker = 0xFFE1 (EXIF data lives here) if (marker === 0xFFE1) { const length = view.getUint16(offset + 2); tags.hasExif = true; tags.exifSegmentSize = length; break; } if (marker === 0xFFDA) break; // Start of scan const segLength = view.getUint16(offset + 2); offset += 2 + segLength; } return tags; } The Canvas trick works because canvas.toBlob() creates a brand-new JPEG from raw pixel data. The browser’s JPEG encoder has no concept of EXIF — it only knows about pixels. So the output file is inherently metadata-free. This is the same technique PixelStrip uses under the hood, with additional optimizations for handling large images without running out of memory. The readExifFromArrayBuffer function shows how EXIF data is physically stored in a JPEG file. Every JPEG is a sequence of segments, each starting with a two-byte marker. The APP1 segment (marker 0xFFE1) contains the EXIF data. By parsing these markers with a DataView, you can inspect exactly what metadata exists without any external library. This is how PixelStrip shows you the “before” view — it reads the raw bytes, extracts each tag, and flags the privacy-sensitive ones (GPS coordinates, device serial numbers) in red. Why I Built PixelStrip After finding my home address embedded in a photo I shared on a forum, I realized most people have no idea what their photos reveal. I had posted a picture of a homelab rack on a tech forum — nothing sensitive, just server hardware. A commenter replied with my approximate street address, pulled directly from the EXIF GPS tags. The photo was geotagged to within 3 meters of my front door. That incident changed how I think about photo sharing. I spent a week auditing every photo I’d posted online over the previous year. About 40% of them still had full GPS data — the ones shared on platforms that don’t auto-strip meta


---
## 5 Free Browser Tools That Replace Desktop Apps

- URL: https://orthogonal.info/free-browser-tools-replace-desktop-apps/
- Date: 2026-03-22
- Category: Tools &amp; Setup
- Summary: Replace Photoshop, Notepad++, and desktop utilities with 5 free browser tools. No downloads, no sign-ups — just open a tab and start working right away.

I built 3 of these tools because I got tired of desktop apps phoning home. After 12 years as a security engineer in Big Tech, I’ve watched network traffic from “offline” desktop apps — the telemetry, the analytics pings, the “anonymous” usage data that includes your file paths and timestamps. When I needed to compress an image or strip EXIF data, I didn’t want to install yet another Electron app that ships a full Chromium browser just to resize a JPEG. So I built browser-only alternatives that do the job without ever touching a network socket. These five tools run entirely in your browser tab. No downloads, no accounts, no servers processing your files. And once loaded, most of them work completely offline. 📌 TL;DR: You don’t need to install an app for everything. These browser-based tools work instantly — no download, no account, no tracking. They run entirely on your device and work offline once loaded. 🎯 Quick Answer: Five free browser-based tools—JSON formatter, image compressor, hash generator, EXIF stripper, and snippet manager—replace desktop apps entirely. They work offline, require no downloads or accounts, and never upload your data to any server. 1. Image Compression → QuickShrink Instead of installing Photoshop or GIMP just to resize an image, open QuickShrink. Drop an image, pick quality (80% is ideal), download. Compresses using the same Canvas API that powers web photo editors. Typical result: 4MB photo → 800KB with no visible difference. 2. Photo Privacy → PixelStrip Before sharing photos on forums or marketplaces, strip the hidden metadata. PixelStrip shows you exactly what’s embedded (GPS, camera model, timestamps) and removes it all with one click. No upload to any server. 3. Code Snippet Manager → TypeFast If you keep a file of frequently-used code blocks, email templates, or canned responses, TypeFast gives you a searchable list with one-click copy. Stores everything in your browser’s localStorage — no cloud sync needed. 4. Focus Timer → FocusForge A Pomodoro timer that adds XP and streaks to make deep work addictive. Three modes: 25, 45, or 60 minutes. Level up from Rookie to Immortal. Available on Google Play for Android. 5. Noise Meter → NoiseLog Turn your phone into a sound level meter that logs incidents and generates reports. Perfect for documenting noise complaints with timestamps and decibel readings. Available on Google Play. Why Browser-Based? No install — works immediately in any browser Private — data stays on your device Fast — loads in milliseconds, not minutes Cross-platform — works on Windows, Mac, Linux, iOS, Android Offline — install as PWA for offline use How Browser-Only Architecture Actually Works Every tool in this list relies on JavaScript Web APIs that ship with modern browsers — no plugins, no downloads. But “runs in the browser” isn’t just marketing. Let me show you the actual architecture that makes these tools work offline, stay private, and perform at near-native speed. The Four Pillars of Client-Side Processing Canvas API — renders and manipulates images pixel by pixel. This is how QuickShrink compresses photos without a server. Web Audio API — captures and analyzes microphone input in real time. NoiseLog uses this to measure decibel levels. File API — reads files from your local disk directly into JavaScript. No upload required. PixelStrip uses this to parse JPEG metadata. localStorage / IndexedDB — persistent storage in the browser. TypeFast saves your snippets here so they survive page reloads. These APIs have been stable across Chrome, Firefox, Safari, and Edge for years. They’re not experimental — they’re the same foundation that powers Google Docs, Figma, and VS Code for the Web. Service Workers: The Offline Engine The secret to making browser tools work offline is the Service Worker — a background script that intercepts network requests and serves cached responses. Here’s the actual pattern I use across all my tools to enable offline functionality: // sw.js — Service Worker for offline-first browser tools const CACHE_NAME = 'tool-cache-v1'; const ASSETS = [ '/', '/index.html', '/app.js', '/style.css', '/manifest.json' ]; // Cache all assets on install self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS)) ); self.skipWaiting(); }); // Clean old caches on activate self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)) ) ) ); self.clients.claim(); }); // Serve from cache first, fall back to network self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((cached) => { return cached || fetch(event.request).then((response) => { // Cache new requests for next offline use const clone = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone)); return response; }); }) ); }); Once the Service Worker caches the app assets, the tool works completely offline — airplane mode, no WiFi, doesn’t matter. The browser loads everything from its local cache. This is how QuickShrink and PixelStrip work on flights and in areas with no connectivity. No server round-trip means zero latency for the user and zero data exposure. localStorage: Persistent State Without a Database TypeFast stores all your snippets in localStorage — a simple key-value store built into every browser. Here’s the core persistence pattern: // Snippet storage using localStorage const STORAGE_KEY = 'typefast_snippets'; function saveSnippets(snippets) { localStorage.setItem(STORAGE_KEY, JSON.stringify(snippets)); } function loadSnippets() { const raw = localStorage.getItem(STORAGE_KEY); return raw ? JSON.parse(raw) : []; } function addSnippet(title, content, tags = []) { const snippets = loadSnippets(); snippets.push({ id: crypto.randomUUID(), title, content, tags, created: Date.now(), used: 0 }); saveSnippets(snippets); } function searchSnippets(query) { const q = query.toLowerCase(); return loadSnippets().filter((s) => s.title.toLowerCase().includes(q) || s.content.toLowerCase().includes(q) || s.tags.some((t) => t.toLowerCase().includes(q)) ); } The beauty of this approach: your data never leaves your browser’s storage directory on disk. No sync server, no account, no authentication flow. The tradeoff is that clearing browser data deletes your snippets — which is why I added an export/import feature that dumps everything to a JSON file you can back up. For most users, localStorage’s 5-10MB limit is more than enough for text snippets. Canvas API: GPU-Accelerated Image Processing Here’s the core of QuickShrink — client-side image compression in about 20 lines of JavaScript: async function compressImage(file, quality = 0.8) { const img = new Image(); const url = URL.createObjectURL(file); await new Promise((resolve) => { img.onload = resolve; img.src = url; }); URL.revokeObjectURL(url); const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/jpeg', quality) ); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = file.name.replace(/\.\w+$/, '-compressed.jpg'); a.click(); } The Canvas API’s toBlob() method does the heavy lifting — it re-encodes the image at whatever quality level you specify. The browser’s built-in JPEG encoder handles the compression using GPU-accelerated rendering. No library, no dependency, no server. WebAssembly is the next frontier. Tools like Squoosh already use WASM to run codecs like MozJPEG and AVIF directly in the browser. This means server-grade compression algorithms can now execute client-side at near-native speed. Expect browser tools to match — and eventually surpass — desktop apps for most image and video processing tasks. Privacy Co


---
## How to Remove GPS Location from Photos Before Sharing Online

- URL: https://orthogonal.info/remove-gps-location-from-photos/
- Date: 2026-03-22
- Category: Tools &amp; Setup
- Summary: Your phone embeds GPS coordinates in every photo. Learn how to remove location metadata before sharing images online to protect your privacy for free.

Every time you take a photo with your phone, the exact GPS coordinates are embedded in the image file. When you share that photo online — on forums, marketplaces, or messaging apps — anyone who downloads it can see exactly where you were standing. Here’s how to remove it in 3 seconds. Quick Fix: Strip All Metadata 📌 TL;DR: Every time you take a photo with your phone, the exact GPS coordinates are embedded in the image file. When you share that photo online — on forums, marketplaces, or messaging apps — anyone who downloads it can see exactly where you were standing. Here’s how to remove it in 3 seconds. 🎯 Quick Answer: Every smartphone photo embeds GPS coordinates in EXIF metadata that reveal your exact location. Remove GPS data in 3 seconds using a browser-based EXIF stripper—no uploads, no installs, no accounts required. Open PixelStrip Drop your photo on the page See the metadata report (GPS coordinates highlighted in red) Click “Strip All Metadata & Download” The downloaded photo looks identical but contains zero hidden data. No GPS, no camera model, no timestamps. What Metadata Is Actually in Your Photos? EXIF metadata was designed for photographers to track camera settings. But smartphones added fields that reveal far more than you’d expect: GPS Latitude/Longitude — accurate to within 3 meters on modern phones Device Model — “iPhone 16 Pro” or “Samsung Galaxy S25” — narrows down who took the photo Date & Time — the exact second the photo was captured Camera Serial Number — a unique identifier that links photos from the same device Thumbnail — a smaller version that may contain content you cropped out Which Platforms Strip Metadata Automatically? Do strip metadata: Facebook, Instagram, Twitter/X, WhatsApp (when sent as photo, not file) Don’t strip metadata: Email, Telegram (file mode), Discord, Forums, Craigslist, eBay, Dropbox, Google Drive shared links If you’re sharing via any channel that doesn’t strip metadata, do it yourself first. 👉 Strip your photos with PixelStrip — no upload, no account, 100% in your browser. Command-Line GPS Removal With ExifTool ExifTool is the Swiss Army knife of metadata manipulation. It reads and writes EXIF, IPTC, XMP, and dozens of other metadata formats across virtually every image type. For GPS removal specifically, it offers surgical precision — you can strip location data while preserving camera settings, copyright info, and other tags you want to keep. # View all GPS-related tags in a photo exiftool -gps:all photo.jpg # View GPS as a Google Maps link exiftool -n -p "https://maps.google.com/?q=$GPSLatitude,$GPSLongitude" photo.jpg # Remove ONLY GPS tags (keep everything else) exiftool -gps:all= photo.jpg # Remove GPS from all JPEGs in a folder exiftool -gps:all= -overwrite_original *.jpg # Remove GPS + create backup of originals exiftool -gps:all= -o stripped/ *.jpg # Nuclear option: remove ALL metadata exiftool -all= photo.jpg # Verify GPS is gone exiftool -gps:all photo.jpg # Should output: (no output = no GPS tags) The key flag is -gps:all= (with the equals sign and no value). This tells ExifTool to set all GPS-related tags to empty, effectively deleting them. The -overwrite_original flag skips creating backup files (ExifTool normally saves photo.jpg_original as a safety net). For batch processing, the wildcard *.jpg handles every JPEG in the current directory. For automated workflows, here’s a shell script that watches a folder and strips GPS from any new images: #!/bin/bash # auto_strip_gps.sh - Watch a folder and strip GPS from new images WATCH_DIR="${1:-.}" PROCESSED_LOG="$WATCH_DIR/.stripped_files" touch "$PROCESSED_LOG" echo "Watching $WATCH_DIR for new images..." while true; do find "$WATCH_DIR" -maxdepth 1 -type f \( -name "*.jpg" -o -name "*.jpeg" -o -name "*.png" \) | while read -r file; do if ! grep -qF "$file" "$PROCESSED_LOG" 2>/dev/null; then echo "Stripping GPS from: $file" exiftool -gps:all= -overwrite_original "$file" 2>/dev/null echo "$file" >> "$PROCESSED_LOG" fi done sleep 5 done And here’s a Python wrapper that gives you more control — logging, error handling, and the ability to selectively preserve certain tags: import subprocess import os import json def get_gps_data(filepath): result = subprocess.run( ["exiftool", "-json", "-gps:all", filepath], capture_output=True, text=True ) if result.returncode != 0: return None data = json.loads(result.stdout) return data[0] if data else None def strip_gps(filepath, keep_backup=False): cmd = ["exiftool", "-gps:all="] if not keep_backup: cmd.append("-overwrite_original") cmd.append(filepath) result = subprocess.run(cmd, capture_output=True, text=True) return result.returncode == 0 def batch_strip_gps(folder, extensions=None): if extensions is None: extensions = {".jpg", ".jpeg", ".tiff", ".png"} results = {"stripped": [], "skipped": [], "errors": []} for fname in os.listdir(folder): ext = os.path.splitext(fname)[1].lower() if ext not in extensions: continue fpath = os.path.join(folder, fname) gps = get_gps_data(fpath) if gps and any(k.startswith("GPS") for k in gps): if strip_gps(fpath): results["stripped"].append(fname) else: results["errors"].append(fname) else: results["skipped"].append(fname) return results # Usage: # results = batch_strip_gps("/path/to/photos") # print(f"Stripped: {len(results['stripped'])}") # print(f"Skipped (no GPS): {len(results['skipped'])}") The Python wrapper adds intelligence that the raw command lacks. It checks whether GPS data actually exists before attempting removal (avoiding unnecessary file writes), tracks which files were processed, and separates results into stripped/skipped/error categories. This is useful when processing large photo libraries where most images may already be clean. Building a Browser-Based GPS Stripper Command-line tools are powerful but require installation. For a zero-install solution, you can build a browser-based GPS stripper that processes photos entirely client-side. The challenge: you need to parse the JPEG binary format, find the GPS IFD (Image File Directory) entries, and nullify them — all without corrupting the rest of the file. // Parse JPEG to find and nullify GPS data // GPS lives in the EXIF APP1 segment (marker 0xFFE1) async function removeGPSFromJPEG(file) { const buffer = await file.arrayBuffer(); const bytes = new Uint8Array(buffer); // Verify JPEG signature if (bytes[0] !== 0xFF || bytes[1] !== 0xD8) { throw new Error("Not a valid JPEG file"); } // Find APP1 (EXIF) segment let offset = 2; while (offset < bytes.length - 1) { if (bytes[offset] !== 0xFF) break; const marker = bytes[offset + 1]; // APP1 = 0xE1 if (marker === 0xE1) { const segLen = (bytes[offset+2] << 8) | bytes[offset+3]; // Check for EXIF header: "Exif\0\0" const header = String.fromCharCode( bytes[offset+4], bytes[offset+5], bytes[offset+6], bytes[offset+7] ); if (header === "Exif") { nullifyGPSInSegment(bytes, offset + 10, segLen - 8); } } // SOS marker = start of image data, stop parsing if (marker === 0xDA) break; // Skip to next segment const len = (bytes[offset+2] << 8) | bytes[offset+3]; offset += 2 + len; } return new Blob([bytes], { type: "image/jpeg" }); } function nullifyGPSInSegment(bytes, tiffStart, length) { // Read byte order: "II" (Intel/little-endian) // or "MM" (Motorola/big-endian) const isLittleEndian = (bytes[tiffStart] === 0x49); function readUint16(pos) { return isLittleEndian ? bytes[pos] | (bytes[pos+1] << 8) : (bytes[pos] << 8) | bytes[pos+1]; } function readUint32(pos) { return isLittleEndian ? bytes[pos] | (bytes[pos+1]<<8) | (bytes[pos+2]<<16) | (bytes[pos+3]<<24) : (bytes[pos]<<24) | (bytes[pos+1]<<16) | (bytes[pos+2]<<8) | bytes[pos+3]; } // Parse IFD0 to find GPS IFD pointer (tag 0x8825) const ifd0Offset = tiffStart + readUint32(tiffStart + 4); const entryCount = readUint16(ifd0Offset); for (let i = 0; i < entryCount; i++) { const entryPos = ifd0Offset + 2 + (i * 12); const tagId = readUint16(entryPos); // Tag 0x8825 = GPS IFD pointer if (tagId === 0x8825) { const gpsOff


---
## Compress Images Without Losing Quality (Free Tool)

- URL: https://orthogonal.info/compress-images-without-losing-quality/
- Date: 2026-03-22
- Category: Tools &amp; Setup
- Summary: Compress JPEG and PNG images up to 80% smaller without visible quality loss using free browser tools. No uploads to servers — your files stay 100% private.

You need to send a photo by email but it’s 8MB. You need to upload a product image but the CMS has a 2MB limit. You need to speed up your website but your hero image is 4MB. The solution is always the same: compress the image. But most tools upload your photo to a random server first. Here’s how to compress images without uploading them anywhere, using only your browser. The 30-Second Method 📌 TL;DR: You need to send a photo by email but it’s 8MB. You need to upload a product image but the CMS has a 2MB limit. You need to speed up your website but your hero image is 4MB. 🎯 Quick Answer: Compress images up to 80% smaller without visible quality loss using browser-based compression that processes photos entirely on your device. Unlike cloud tools like TinyPNG, your images never leave your browser. Open QuickShrink Drag your image onto the page (or click to select) Adjust the quality slider — 80% gives great results for most photos Click Download That’s it. Your image never leaves your device. The compression happens entirely in your browser using the Canvas API — the same technology that powers web-based photo editors. What Quality Setting Should I Use? 80% — Best for most photos. Reduces file size by 40-60% with no visible difference. This is what most professional websites use. 60% — Good for thumbnails, social media, and email attachments. 70-80% smaller. You might notice slight softening if you zoom in, but it looks perfect at normal viewing size. 40% — Maximum compression. 85-90% smaller. Visible artifacts on close inspection, but fine for previews and low-bandwidth situations. 100% — No compression. Use this only if you need to convert PNG to JPEG without quality loss. Why Not Use TinyPNG or Squoosh? TinyPNG uploads your image to their servers. For personal photos or client work, that’s a privacy concern. Also limited to 20 free compressions per month. Squoosh (by Google) is excellent and also runs client-side. But it’s heavier — loads a WASM codec and has a complex UI. If you just want to shrink a photo fast, it’s overkill. QuickShrink is a single HTML file, loads in under 200ms, works offline, and does one thing: make your image smaller. No account, no limits, no tracking. 👉 Try QuickShrink — free, forever, private by design. How Image Compression Works Under the Hood When you drag a photo into QuickShrink and move the quality slider, here’s what actually happens at the code level. The browser’s Canvas API does the heavy lifting, but understanding the mechanics helps you choose the right settings. The compression pipeline has three stages: Decode — the browser reads your JPEG/PNG file and decompresses it into raw pixel data (an array of RGBA values) Render — those pixels are drawn onto an invisible HTML5 Canvas element Re-encode — the Canvas exports the image as a new JPEG at your chosen quality level The quality parameter controls how aggressively the JPEG encoder discards visual information. JPEG compression works by dividing the image into 8×8 pixel blocks, applying a Discrete Cosine Transform (DCT) to each block, and then quantizing the frequency coefficients. Lower quality means more aggressive quantization — more data thrown away, smaller file, more artifacts. The lossy vs. lossless distinction matters here. JPEG is always lossy — every re-encode loses some data. PNG is lossless — it compresses without discarding anything, which is why PNG files are larger. When QuickShrink converts a PNG to JPEG, you get dramatic size reduction because you’re switching from lossless to lossy compression. Here’s the JPEG quality vs. file size curve that explains the diminishing returns: 100% → 95% — saves 30-40% file size with zero visible change 95% → 80% — saves another 30-40% with no visible change at normal viewing 80% → 60% — saves another 20-30% with slight softening visible at 100% zoom 60% → 40% — saves only 10-15% more but introduces visible blockiness This is why 80% is the sweet spot. You capture the steepest part of the compression curve — maximum size reduction before visible quality loss. Here’s a complete browser-based image compressor in about 30 lines of JavaScript: // Complete client-side image compressor document.getElementById('file-input').addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return; // Decode the image const img = new Image(); const objectUrl = URL.createObjectURL(file); await new Promise(resolve => { img.onload = resolve; img.src = objectUrl; }); URL.revokeObjectURL(objectUrl); // Render to canvas const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; canvas.getContext('2d').drawImage(img, 0, 0); // Re-encode as JPEG at 80% quality const quality = 0.8; const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/jpeg', quality) ); // Show results const savings = ((1 - blob.size / file.size) * 100).toFixed(1); console.log(`Original: ${(file.size/1024).toFixed(0)}KB`); console.log(`Compressed: ${(blob.size/1024).toFixed(0)}KB`); console.log(`Saved: ${savings}%`); // Trigger download const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'compressed-' + file.name; a.click(); }); That’s the entire core of QuickShrink. Everything else — the drag-and-drop UI, the quality slider, the preview — is just interface around these 30 lines. Batch Compression Script Browser tools work great for one-off compressions, but if you need to process an entire folder of images, command-line scripts are faster. Here are two approaches I use regularly. Python script using Pillow — good for cross-platform use and when you need precise control: #!/usr/bin/env python3 # Batch compress JPEG images using Pillow import os, sys from pathlib import Path from PIL import Image def compress_images(input_dir, output_dir, quality=80): Path(output_dir).mkdir(parents=True, exist_ok=True) total_saved = 0 for filename in os.listdir(input_dir): if not filename.lower().endswith(('.jpg', '.jpeg', '.png')): continue input_path = os.path.join(input_dir, filename) output_path = os.path.join(output_dir, filename) original_size = os.path.getsize(input_path) img = Image.open(input_path) if img.mode == 'RGBA': img = img.convert('RGB') img.save(output_path, 'JPEG', quality=quality, optimize=True) new_size = os.path.getsize(output_path) saved = original_size - new_size total_saved += saved print(f"{filename}: {original_size//1024}KB -> {new_size//1024}KB") print(f"Total saved: {total_saved // 1024}KB") if __name__ == '__main__': compress_images(sys.argv[1], sys.argv[2], int(sys.argv[3]) if len(sys.argv) > 3 else 80) Shell one-liner using ImageMagick — fastest option if you already have it installed: # Compress all JPEGs in current directory to 80% quality mkdir -p compressed for f in *.jpg *.jpeg *.png; do [ -f "$f" ] || continue convert "$f" -quality 80 -strip "compressed/$f" echo "$f: $(du -k "$f" | cut -f1)KB -> $(du -k "compressed/$f" | cut -f1)KB" done The -strip flag removes all metadata (EXIF, ICC profiles, etc.), which often saves an additional 20-50KB per image. If you want to keep color profiles, drop that flag. Comparing output sizes at different quality levels for a typical 4MB smartphone photo: Quality 95 — 2.4MB (40% reduction) Quality 80 — 820KB (80% reduction) ← the sweet spot Quality 60 — 480KB (88% reduction) Quality 40 — 320KB (92% reduction) Quality 20 — 180KB (95% reduction, visible artifacts) The jump from 95 to 80 gives you the most bang for your buck — nearly halving the file size from the 95% baseline with no perceptible quality loss. Why I Built QuickShrink Instead of Using Existing Tools Every free image compressor I could find had the same problem: they upload your files to their server. TinyPNG, Compressor.io, iLoveIMG — they all send your images over the network, process them on their infrastructure, and send them back. That’s a privacy problem. Think about what you’re compressing. Product photos for yo


---
## NoiseLog: A Sound Meter App for Noisy Neighbors

- URL: https://orthogonal.info/noiselog-sound-meter/
- Date: 2026-03-22
- Category: Tools &amp; Setup
- Summary: NoiseLog is a browser-based sound meter I built to track noisy neighbors. Uses the Web Audio API with real-time dB logging and zero data collection.

When I complained about noise to my building manager, they asked for evidence. “It’s loud” wasn’t enough. They wanted dates, times, and decibel readings. So I built an app that gives you all three — and generates a report you can actually hand to someone. The Noise Complaint Trap 📌 TL;DR: When I complained about noise to my building manager, they asked for evidence. “It’s loud” wasn’t enough. They wanted dates, times, and decibel readings. 🎯 Quick Answer: NoiseLog is a sound meter app that records decibel readings with timestamps and automatically generates formatted noise complaint reports with date, time, duration, and dB levels—ready to submit to landlords or local authorities. Here’s how noise complaints typically go: you’re frustrated, you call the landlord or the city, they say “we’ll look into it,” nothing happens. Why? Because verbal complaints carry almost zero weight. Without documentation — specific dates, times, duration, and measured intensity — you’re just another person saying “it’s too loud.” Professional sound level meters cost $200+. An acoustic engineering assessment starts at $500. Most people just suffer in silence (pun intended) or escalate to a confrontation. Neither is a great outcome. Your Phone Microphone Is Good Enough Modern smartphone microphones are surprisingly capable. They won’t match a calibrated Type I sound meter, but for documenting noise levels in the 40–100 dB range — which covers everything from a loud TV to construction equipment — they’re more than adequate. Courts and housing authorities don’t require laboratory-grade measurements; they require consistent, timestamped records. NoiseLog uses your phone’s microphone to capture ambient sound, processes it through a standard A-weighted decibel calculation, and displays the result in real time. Three Screens, One Workflow Sound Meter. A live dB reading with a 60-second rolling chart. Color bands show you whether the noise level is safe (green), moderate (yellow), loud (orange), or harmful (red, 85+ dB). The day limit indicator shows if you’ve been exposed to noise above safe thresholds. Incidents. One tap logs the current noise level with a timestamp. “Tuesday, 11:47 PM, 78 dB.” Over a few days or weeks, you build a pattern. “This happens every weeknight between 11 PM and 2 AM, averaging 72 dB.” That’s not a complaint — that’s evidence. Report. Generate a formatted summary of all logged incidents. Dates, times, decibel readings, in a clean layout you can screenshot, print, or share. Hand it to your landlord, attach it to a noise ordinance complaint, or bring it to a mediator. Structured data is harder to ignore than “my neighbor is loud.” Beyond Neighbors Noise complaints are the obvious use case, but people have found others: Workplace safety — OSHA requires hearing protection above 85 dB. NoiseLog helps document whether your factory floor or machine shop meets standards Event planning — check if your venue stays within local noise ordinance limits during rehearsals Parenting — curiosity check: how loud is that toy your kid loves? (Often shockingly loud) Musicians — monitor rehearsal room levels to protect hearing Real estate — measure ambient noise levels in a potential apartment before signing a lease Free vs. Pro The free version is fully functional — real-time metering, incident logging, reports. The only friction is a short video ad when you start a measurement session. The Pro subscription ($1.99/month) removes ads, unlocks unlimited incident storage, and enables detailed CSV export for anyone who needs to submit formal documentation. How Decibel Measurement Works in the Browser NoiseLog’s core measurement engine runs entirely in the browser using the Web Audio API. No server, no uploads, no third-party SDKs touching your microphone data. Here’s the signal chain that turns raw microphone input into a decibel reading: // Web Audio API: microphone to dB meter async function initMeter() { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const audioCtx = new AudioContext(); const source = audioCtx.createMediaStreamSource(stream); const analyser = audioCtx.createAnalyser(); analyser.fftSize = 2048; source.connect(analyser); const buffer = new Float32Array(analyser.fftSize); function calculateDB() { analyser.getFloatTimeDomainData(buffer); // RMS (Root Mean Square) calculation let sumSquares = 0; for (let i = 0; i < buffer.length; i++) { sumSquares += buffer[i] * buffer[i]; } const rms = Math.sqrt(sumSquares / buffer.length); // Convert RMS to decibels const db = 20 * Math.log10(Math.max(rms, 1e-10)); // A-weighting approximation for human-perceived loudness // Boosts mid-frequencies (1-6 kHz) where human hearing is most sensitive const aWeighted = applyAWeighting(db, analyser, buffer); return { raw: db.toFixed(1), aWeighted: aWeighted.toFixed(1), rms: rms }; } // A-weighting filter approximation function applyAWeighting(dbValue, analyser, timeDomain) { const freqData = new Float32Array(analyser.frequencyBinCount); analyser.getFloatFrequencyData(freqData); const nyquist = audioCtx.sampleRate / 2; let weightedSum = 0; let totalEnergy = 0; for (let i = 0; i < freqData.length; i++) { const freq = (i / freqData.length) * nyquist; const energy = Math.pow(10, freqData[i] / 10); // Simplified A-weighting curve const weight = aWeightCoeff(freq); weightedSum += energy * weight; totalEnergy += energy; } const correction = totalEnergy > 0 ? 10 * Math.log10(weightedSum / totalEnergy) : 0; return dbValue + correction; } function aWeightCoeff(f) { // IEC 61672 A-weighting approximation const f2 = f * f; const num = 1.4866e8 * f2 * f2; const den = (f2 + 424.36) * Math.sqrt((f2 + 11599.29) * (f2 + 544496.41)) * (f2 + 148693636); const ra = num / den; return ra * ra * 1.9997; } // Update loop at 30fps setInterval(() => { const reading = calculateDB(); updateDisplay(reading); logToHistory(reading); }, 33); } The key steps: getUserMedia grabs the microphone stream, the AnalyserNode provides raw audio samples, and then it’s just math. RMS gives you the average energy in the signal, and the log10 conversion maps it to the decibel scale humans are used to. The A-weighting filter is critical — without it, you’d measure low-frequency rumble (like HVAC systems) at the same level as a conversation, which isn’t how humans perceive loudness. Calibration and Accuracy Let’s be honest: your phone microphone is not a professional SPL meter. It has a limited dynamic range, nonlinear frequency response, and automatic gain control that most browsers can’t fully disable. But here’s the thing — for the purpose of documenting noise complaints, it doesn’t need to be perfect. It needs to be consistent and timestamped. I calibrated NoiseLog against a UNI-T UT353 mini sound level meter (about $30 on most electronics sites) in my apartment. The process was straightforward: play pink noise at a known level, read both meters simultaneously, and calculate the offset. Here’s what I found across different volume levels: 40–50 dB (quiet room): Phone read 2–4 dB lower than the SPL meter. Microphone sensitivity drops off at very low levels. 50–70 dB (conversation to loud TV): Within ±2 dB. This is the sweet spot where phone microphones are most accurate. 70–85 dB (loud music, vacuum cleaner): Within ±3 dB. Still reliable enough for evidence. 85+ dB (harmful levels): Phone microphone begins to clip. Readings plateau around 90–95 dB regardless of actual level. If you’re measuring construction noise, the phone underreports. For the 50–85 dB range — which covers virtually every noise complaint scenario (loud neighbors, barking dogs, late-night parties) — a typical smartphone accuracy of ±3 dB is more than adequate. Housing authorities and mediators aren’t looking for laboratory precision. They’re looking for a pattern: repeated measurements at specific times showing levels above the local ordinance threshold. NoiseLog includes a calibration offset setting so you can adjust readings if you


---
## FocusForge: How Gamification Tricked Me Into Deep Focus

- URL: https://orthogonal.info/focusforge-focus-timer/
- Date: 2026-03-22
- Category: Tools &amp; Setup
- Summary: FocusForge adds XP, levels, and streaks to the Pomodoro Technique. How gamification turned a simple timer into my most effective deep focus tool.

I’ve downloaded at least ten Pomodoro timer apps over the years. I used each one for about three days before forgetting it existed. Then I built FocusForge, added XP and levels, and accidentally created a focus habit I can’t stop. The Pomodoro Problem 📌 TL;DR: I’ve downloaded at least ten Pomodoro timer apps over the years. I used each one for about three days before forgetting it existed. Then I built FocusForge, added XP and levels, and accidentally created a focus habit I can’t stop. 🎯 Quick Answer: FocusForge is a gamified Pomodoro timer that awards XP and levels for completed focus sessions. The RPG-style progression system creates lasting deep focus habits by turning productivity into a game with visible streaks and milestones. The Pomodoro Technique is elegant: work for 25 minutes, take a 5-minute break, repeat. After four cycles, take a longer break. It’s been around since the late 1980s and it works — when you actually do it. The problem isn’t the technique. It’s the timer. A countdown clock gives you no reason to come back tomorrow. There’s no cost to skipping a day, no reward for consistency, no progression. It’s like a gym with no mirror — you can’t see if you’re making progress, so you stop going. XP, Levels, and the Streak That Won’t Let You Quit FocusForge adds three things to the standard Pomodoro timer: 1. Experience Points. Every completed focus session earns XP. A Quick 25 gives you 25 XP. A Deep 45 gives you 50. A Marathon 60 gives you 75. It’s a simple formula, but watching a number go up is an irrationally powerful motivator. 2. Levels. You start as a Rookie. At 100 XP, you become an Apprentice. Then Journeyman, Expert, Master, Legend, and finally Immortal. Each level has its own color and badge. It’s completely meaningless — and completely addictive. I’m a Master. I refuse to let it drop. 3. Daily Streaks. Complete at least one focus session per day and your streak increments. Miss a day and it resets to zero. This is the mechanic that Duolingo used to build a $12 billion company. It works because loss aversion is stronger than the desire for gain — you don’t want to break a 30-day streak more than you want to skip a 25-minute session. What It Looks Like The main screen is a large countdown timer with three mode buttons: Quick 25, Deep 45, Marathon 60. Below the timer, a motivational quote rotates — Stoic philosophy from Marcus Aurelius and Seneca, productivity wisdom from Cal Newport and James Clear. It sounds cheesy. It works at 6 AM when you don’t want to start. The Stats tab shows your level, XP progress bar, total sessions completed, and streak calendar. The Settings tab lets you upgrade to Pro ($1.99 one-time) to remove ads and unlock custom durations. Why Not Just Use the Phone’s Built-in Timer? You could. But a phone timer doesn’t track your history, doesn’t reward consistency, and doesn’t create a feedback loop. FocusForge turns “I should focus for 25 minutes” into “I need 15 more XP to reach Legend.” The outcome is the same — deep work — but the motivation mechanism is completely different. The Code Behind the Focus Loop FocusForge’s timer isn’t a simple setInterval countdown. It’s a state machine with four states — idle, running, break, and back to running — and each transition triggers side effects like XP calculation and sound notifications. Here’s the core logic I use in the web prototype: // Timer State Machine const STATES = { IDLE: 'idle', RUNNING: 'running', BREAK: 'break' }; let state = STATES.IDLE; let streak = 0; let totalXP = 0; let level = 1; // XP calculation with streak multiplier and difficulty bonus function calculateXP(duration) { const baseXP = duration; // 1 XP per minute const streakMultiplier = 1 + Math.min(streak, 30) * 0.05; // +5% per streak day, cap 30 const difficultyBonus = duration >= 60 ? 1.5 : duration >= 45 ? 1.2 : 1.0; return Math.round(baseXP * streakMultiplier * difficultyBonus); } // Level thresholds using exponential curve function xpForLevel(lvl) { return Math.floor(10 * Math.pow(1.5, lvl)); } // Check and apply level-ups function applyXP(earned) { totalXP += earned; while (totalXP >= xpForLevel(level)) { totalXP -= xpForLevel(level); level++; playSound(880, 0.3); // celebration tone } } // Sound notification using Web Audio API function playSound(freq, duration) { const ctx = new (window.AudioContext || window.webkitAudioContext)(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.value = freq; gain.gain.setValueAtTime(0.3, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + duration); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + duration); } // State transitions function transition(action) { if (state === STATES.IDLE && action === 'start') { state = STATES.RUNNING; startCountdown(); } else if (state === STATES.RUNNING && action === 'complete') { const xp = calculateXP(selectedDuration); applyXP(xp); playSound(660, 0.5); // completion chime state = STATES.BREAK; startBreak(); } else if (state === STATES.BREAK && action === 'resume') { state = STATES.RUNNING; startCountdown(); } } The key insight is that the state machine enforces the Pomodoro rhythm automatically. You can’t skip the break, and you can’t get XP without completing a full cycle. The streakMultiplier caps at 30 days because I found that beyond that point, the habit is self-sustaining — you don’t need the extra incentive. Designing the Reward System My first version used linear XP — every session gave you the same flat reward. It worked for about two weeks and then engagement fell off a cliff. The problem is predictability: when you know exactly what you’ll earn, the dopamine loop breaks. There’s no surprise, no variable reward. The fix was an exponential level curve. Early levels come fast — you hit Level 3 in your first day. But each subsequent level requires roughly 50% more XP than the last. This creates the “near miss” psychology that keeps slot machines profitable and Duolingo sticky: you’re always tantalizingly close to the next milestone. I tested three different XP curves with a small group of friends who agreed to use the app daily for a month: Linear (flat 25 XP per session): Average engagement dropped to 40% by week 3. People said it felt “pointless” after hitting Level 5. Logarithmic (diminishing returns): Even worse — people felt punished for playing more. Engagement cratered by week 2. Exponential with streak bonus: 78% of testers were still using the app daily at day 30. The streak multiplier made each consecutive day feel more valuable. The other trick is showing progress toward the next level constantly. The XP bar is always visible on the main screen, and it fills in real time as you complete sessions. After a session, a popup shows exactly how much XP you earned and how close you are to leveling up. If you’re within one session of the next level, a subtle glow animation appears on the progress bar. It’s shameless psychological manipulation — and I’m the one being manipulated, happily. All game state persists in localStorage with a simple schema: // localStorage schema for game state persistence const STORAGE_KEY = 'focusforge_state'; function saveState() { const state = { totalXP: totalXP, level: level, streak: streak, lastSessionDate: new Date().toISOString().split('T')[0], sessionsCompleted: sessionsCompleted, history: sessionHistory.slice(-90) // keep 90 days }; localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } function loadState() { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return defaultState(); const saved = JSON.parse(raw); // Check streak continuity const today = new Date().toISOString().split('T')[0]; const yesterday = new Date(Date.now() - 86400000) .toISOString().split('T')[0]; if (saved.lastSessionDate !== today && saved.lastSessionDate !== yesterday) { saved.streak = 0; // streak broken } return saved; } I deliberately limited the history t


---
## TypeFast: Snippet Manager Without Electron Bloat

- URL: https://orthogonal.info/typefast-snippet-manager/
- Date: 2026-03-22
- Category: Tools &amp; Setup
- Summary: TypeFast is a lightweight snippet manager for developers who hate bloated Electron apps. Keyboard-driven, instant search, and under 5 MB total size.

I needed a place to store code snippets, email templates, and frequently pasted text blocks. Everything I found was either a full IDE extension, a note-taking app in disguise, or yet another Electron app eating 200MB of RAM. So I built TypeFast — a snippet manager that runs in a browser tab. The Snippet Graveyard Problem 📌 TL;DR: I needed a place to store code snippets, email templates, and frequently pasted text blocks. Everything I found was either a full IDE extension, a note-taking app in disguise, or yet another Electron app eating 200MB of RAM. So I built TypeFast — a snippet manager that runs in a browser tab. 🎯 Quick Answer: TypeFast is a lightweight, browser-based snippet manager for storing and instantly pasting code snippets and frequently used text. Unlike Electron-based alternatives, it uses zero system resources when idle and works entirely in your browser. Every developer has one. A folder called snippets or useful-stuff sitting somewhere in their home directory. A Notion page titled “Code Templates” that hasn’t been updated since 2023. Three GitHub Gists they can’t find because they never gave them proper names. Slack messages to themselves that got buried under 400 notifications. The common thread: the tool was never designed for quick retrieval. Notion is a document editor. Gists are for sharing, not searching. Slack is for messaging. Using them as snippet managers is like using a spreadsheet as a to-do list — it technically works, but the friction kills you. What TypeFast Actually Does TypeFast has exactly four features: Add a snippet — give it a title, a category, paste the content Find a snippet — type in the search bar, or filter by category tab Copy a snippet — one click, it’s on your clipboard, a “✅ Copied!” confirmation appears Edit or delete — because snippets evolve That’s it. No folders, no tags cloud, no sharing, no collaboration, no AI suggestions. Just a fast, searchable list with a copy button. The Technical Non-Architecture TypeFast is a single HTML file. No React, no Vue, no build step. The entire application — HTML, CSS, and JavaScript — weighs about 10KB. It stores data in localStorage, which means: No server, no database, no API calls Data persists across browser sessions No account, no sync, no privacy concerns Works offline (it’s also a PWA) The trade-off is obvious: your snippets live only in that browser, on that device. If you clear your browser data, they’re gone. For most people, this is fine — snippets aren’t precious documents. But if you want durability, export them (coming in a future update) or just keep the tab pinned. How the App Architecture Works Most web apps start with npx create-react-app and immediately inherit thousands of dependencies, a build pipeline, and a node_modules folder heavier than the app itself. TypeFast takes the opposite approach: vanilla JavaScript with zero dependencies, organized around an event-driven pattern that would look familiar to anyone who wrote web apps before the framework era. The DOM manipulation strategy is intentionally boring. Instead of a virtual DOM or reactive bindings, TypeFast uses document.createElement() for building snippet cards and direct property assignment for updates. When the snippet list changes, the app clears the container and rebuilds it. For a list of a few hundred items, this is imperceptibly fast — the browser’s layout engine handles it in under a frame. State management is a plain JavaScript object that gets serialized to localStorage on every mutation. Here’s the actual core data model: // Core data model — everything TypeFast needs const AppState = { snippets: [], categories: ['General'], activeCategory: 'All', searchQuery: '' }; // Persist to localStorage on every mutation function saveState() { localStorage.setItem('typefast_data', JSON.stringify({ snippets: AppState.snippets, categories: AppState.categories })); } // Hydrate on startup function loadState() { const saved = localStorage.getItem('typefast_data'); if (saved) { const data = JSON.parse(saved); AppState.snippets = data.snippets || []; AppState.categories = data.categories || ['General']; } } That’s the entire state layer. No Redux store with actions and reducers. No Vuex modules. No React context providers wrapping five levels deep. A single object, two functions, and localStorage as the persistence layer. When something changes, call saveState(). When the page loads, call loadState(). The simplicity is the feature — there are zero state synchronization bugs because there’s only one source of truth. Code Walkthrough: The Snippet Engine The search and filter system is the heart of TypeFast. Every keystroke in the search bar triggers a filter pass across all snippets. Here’s the actual implementation: function filterSnippets() { const query = AppState.searchQuery.toLowerCase(); const category = AppState.activeCategory; return AppState.snippets.filter(snippet => { const matchesCategory = category === 'All' || snippet.category === category; const matchesSearch = !query || snippet.title.toLowerCase().includes(query) || snippet.content.toLowerCase().includes(query) || snippet.category.toLowerCase().includes(query); return matchesCategory && matchesSearch; }); } It searches across title, content, and category simultaneously. No fancy indexing, no search library — just Array.filter() and String.includes(). For collections under a few thousand snippets, this brute-force approach is faster than the overhead of maintaining a search index. The more interesting piece is the template variable system. TypeFast lets you embed dynamic placeholders in your snippets that get expanded at copy time. Type {{date}} in a snippet and it becomes today’s date when you copy it: // Template variables — type {{date}} and get today's date const TEMPLATE_VARS = { '{{date}}': () => new Date().toISOString().split('T')[0], '{{time}}': () => new Date().toLocaleTimeString(), '{{timestamp}}': () => Date.now().toString(), '{{uuid}}': () => crypto.randomUUID(), '{{clipboard}}': async () => { try { return await navigator.clipboard.readText(); } catch { return '{{clipboard}}'; // fallback if permission denied } } }; async function expandTemplateVars(text) { let result = text; for (const [pattern, resolver] of Object.entries(TEMPLATE_VARS)) { if (result.includes(pattern)) { const value = await resolver(); result = result.replaceAll(pattern, value); } } return result; } The {{clipboard}} variable is particularly useful: it reads the current clipboard content and injects it into the snippet. So you can create a template like git commit -m "{{clipboard}}", copy some text, then copy the snippet, and the clipboard content gets wrapped in the git command. The copy-to-clipboard function processes all template variables before writing to the clipboard, so the user always gets the fully expanded version. The one-click copy includes visual feedback so you know something happened: async function copySnippet(snippetId) { const snippet = AppState.snippets.find(s => s.id === snippetId); if (!snippet) return; const expanded = await expandTemplateVars(snippet.content); await navigator.clipboard.writeText(expanded); // Visual feedback const btn = document.querySelector(`[data-copy="${snippetId}"]`); const original = btn.textContent; btn.textContent = '✅ Copied!'; btn.classList.add('copied'); setTimeout(() => { btn.textContent = original; btn.classList.remove('copied'); }, 1500); } The 1500ms timeout for the feedback animation is deliberate — long enough to register visually, short enough that it resets before you need to copy another snippet. The copied CSS class triggers a brief green highlight animation on the button. Small detail, but it’s the difference between “did that work?” and “done, next.” Performance: TypeFast vs Electron Alternatives The entire reason TypeFast exists is that snippet managers shouldn’t need Electron. Here’s how it compares against typical alternatives: MetricTypeFastElectron Snippet ManagerVS Code Exten


---
## PixelStrip: Stop Photos Broadcasting Your Location

- URL: https://orthogonal.info/pixelstrip-exif-remover/
- Date: 2026-03-22
- Category: Tools &amp; Setup
- Summary: PixelStrip removes EXIF metadata from photos before sharing. Strips GPS, camera info, and timestamps entirely in your browser — nothing gets uploaded.

Every photo taken on a smartphone embeds invisible metadata — including GPS coordinates accurate to within a few meters. PixelStrip strips it all out before you share. Zero upload, zero tracking, zero excuses. A Quick Experiment 📌 TL;DR: Every photo taken on a smartphone embeds invisible metadata — including GPS coordinates accurate to within a few meters. PixelStrip strips it all out before you share. Zero upload, zero tracking, zero excuses. 🎯 Quick Answer: PixelStrip removes all EXIF metadata from photos—including GPS coordinates, camera info, and timestamps—entirely in your browser. Your photos never touch a server, making it the safest way to strip location data before sharing online. Pick any photo from your camera roll. Right-click it on your computer, open Properties (Windows) or Get Info (Mac), and look for the GPS fields. If location services were on when you took the photo — and they almost certainly were — you’ll see latitude and longitude coordinates that pinpoint exactly where you were standing. Now imagine you posted that photo on a forum, sold something with it on Craigslist, or sent it in a group chat that got forwarded around. Anyone who saves the image can extract those coordinates and drop them into Google Maps. They’ll see your home, your office, your kid’s school. This isn’t theoretical. It’s happened to journalists, activists, and abuse victims. And it’s happening to you right now, every time you share an unstripped photo. What’s Hiding in Your Photos The EXIF (Exchangeable Image File Format) standard was designed in the 1990s for digital cameras. It stores useful technical data — aperture, shutter speed, focal length. But smartphones added fields that were never meant to be shared publicly: GPS coordinates — latitude, longitude, altitude, and sometimes direction Device fingerprint — phone make, model, OS version, unique camera serial number Timestamps — date and time of capture, modification history Thumbnail images — a smaller version of the original, sometimes containing content you cropped out Software chain — every app that touched the image Social media platforms like Facebook, Instagram, and Twitter strip EXIF data on upload — but email, messaging apps, forums, file-sharing services, and most CMS platforms do not. How PixelStrip Works PixelStrip parses the JPEG binary structure directly in your browser using JavaScript. It identifies EXIF markers (APP1 segments), IFD entries, and GPS sub-IFDs, then displays what it found with clear warning labels. When you click “Strip All Metadata,” the image is re-rendered through an HTML5 Canvas — which by design does not preserve EXIF data — and exported as a clean JPEG. The visual content is identical; the metadata is gone. No server involved. No upload. The file never leaves your browser tab. The Technical Architecture PixelStrip doesn’t just run the image through a Canvas and re-export it (though that would strip EXIF data too). Instead, it parses the JPEG binary structure directly, which lets it show you exactly what metadata is present before removing anything. Here’s how the pipeline works at the code level. A JPEG file is a sequence of binary segments, each starting with a two-byte marker. The image data lives in the SOS (Start of Scan) segment. Metadata lives in APP segments — specifically APP1 (0xFFE1), which contains the EXIF data. PixelStrip reads the file as an ArrayBuffer and walks through these segments to find and parse the EXIF block. Inside the EXIF APP1 segment, data is organized as IFD (Image File Directory) entries — essentially key-value pairs. IFD0 contains basic image info (camera make, software). The GPS sub-IFD (linked from IFD0) contains the location tags: GPSLatitude, GPSLongitude, GPSAltitude, GPSTimeStamp, and others. Each IFD entry has a tag ID, a data type, a count, and either the value itself or an offset to where the value is stored. Here’s a simplified example of reading EXIF data from a File object in the browser: async function readExif(file) { const buffer = await file.arrayBuffer(); const view = new DataView(buffer); // Verify JPEG magic bytes: 0xFFD8 if (view.getUint16(0) !== 0xFFD8) { throw new Error('Not a JPEG file'); } // Walk segments looking for APP1 (EXIF) let offset = 2; while (offset < view.byteLength) { const marker = view.getUint16(offset); if (marker === 0xFFE1) { // Found APP1 segment - parse EXIF const length = view.getUint16(offset + 2); const exifData = buffer.slice(offset + 4, offset + 2 + length); return parseExifIFD(new DataView(exifData)); } // Skip to next segment const segLength = view.getUint16(offset + 2); offset += 2 + segLength; } return null; // No EXIF found } The parseExifIFD function then walks the IFD entries, reading tag IDs and extracting values. GPS coordinates are stored as rational numbers (numerator/denominator pairs) in degrees, minutes, and seconds format. Converting to decimal coordinates requires: degrees + minutes/60 + seconds/3600. To strip the metadata, PixelStrip identifies the APP1 segment boundaries and reconstructs the JPEG without it — copying the SOI marker, skipping APP1, and then copying all remaining segments (DQT, SOF, DHT, SOS, and the compressed image data) intact. The visual image data is never decoded or re-encoded, so there’s zero quality loss. Why Client-Side Processing Matters The irony of most metadata removal tools is hard to overstate: they ask you to upload your photo to their server to remove the data that reveals your location. You’re sending your GPS coordinates to a third party in order to prevent third parties from seeing your GPS coordinates. PixelStrip’s architecture eliminates this entirely. The processing pipeline runs 100% in your browser: File API — reads the image from your local disk into JavaScript memory ArrayBuffer — provides raw binary access to parse JPEG segments Parse — walks the binary structure to find and display EXIF data Strip — reconstructs the JPEG without the APP1 (EXIF) segment Blob — wraps the clean binary data into a downloadable file Download — triggers a browser download with no network request Open your browser’s Network tab while using PixelStrip. You’ll see zero outgoing requests after the initial page load. The JavaScript, CSS, and HTML are served once, cached by your browser, and everything after that happens locally. It even works in airplane mode. Compare this to server-side alternatives like several popular EXIF removal websites. Their typical flow is: upload your image (sending your GPS data over the network), wait for server processing, download the stripped version. Even with HTTPS, your photo sits on their server during processing. Their privacy policy might say they delete it “promptly” — but you have no way to verify that. And if their server is compromised, your photos (with location data) are exposed. Client-side processing isn’t just a privacy feature — it’s also faster. There’s no upload latency, no server queue, no download wait. Stripping metadata from a 5MB photo takes under 100ms locally. The same operation through a server-based tool takes 3-5 seconds minimum, most of that being network transfer time. Building PixelStrip: Lessons Learned I started this project after reading about a journalist whose source was identified through photo EXIF data. The source had sent photos to document conditions at a facility, and the GPS coordinates in those photos led directly back to them. That story stuck with me — not because the technology was exotic, but because the fix was so simple. Strip the metadata before sharing. The problem was that no tool made it easy to do privately. Building a JPEG parser from scratch taught me that the JPEG binary format is messy in practice, even though the spec is straightforward in theory. Every camera manufacturer embeds EXIF slightly differently. Some use big-endian byte order, others use little-endian — and the byte order is specified per-segment, so you have to check it for each EXIF block. I spent an


---
## QuickShrink: Browser Image Compressor, No Uploads

- URL: https://orthogonal.info/quickshrink-image-compressor/
- Date: 2026-03-22
- Category: Tools &amp; Setup
- Summary: I built QuickShrink to compress images without uploading them. Runs 100% in your browser using canvas APIs — fast, private, and free to use.

I got tired of uploading personal photos to random websites just to shrink them. So I built QuickShrink — an image compressor that runs entirely in your browser. Your images never touch a server. The Dirty Secret of “Free” Image Compressors 📌 TL;DR: I got tired of uploading personal photos to random websites just to shrink them. So I built QuickShrink — an image compressor that runs entirely in your browser. Your images never touch a server. 🎯 Quick Answer: QuickShrink compresses images up to 80% in your browser with zero server uploads. Unlike TinyPNG or Squoosh, your photos never leave your device, making it the most private image compression tool available. Go ahead and Google “compress image online.” You’ll find dozens of tools, all with the same pitch: drop your image, we’ll compress it, download the result. Here’s what they don’t tell you: your photo gets uploaded to their server. A server in a data center you’ve never seen, governed by a privacy policy you’ve never read, in a jurisdiction you might not even recognize. That family photo (which might be broadcasting your GPS location), that screenshot of your bank statement, that product image for your client — it’s now sitting on someone else’s disk. Some of these services explicitly state they delete uploads after an hour. Others are silent on the matter. A few have been caught in breaches. The point isn’t that they’re malicious — it’s that there’s no reason for the upload to happen in the first place. The Canvas API Makes Servers Unnecessary Modern browsers ship with the Canvas API — a powerful image processing engine built into Chrome, Firefox, Safari, and Edge. It can decode an image, manipulate its pixels, and re-encode it at any quality level. All of this happens in memory, on your device, using your CPU. QuickShrink leverages this. When you drop an image: The browser reads the file into memory (no network request) A Canvas element renders the image at its native resolution canvas.toBlob() re-encodes it as JPEG at your chosen quality (10%–100%) You download the result directly from browser memory Total data transmitted over the network: zero bytes. Under the Hood: How Canvas API Compression Actually Works To understand why browser-based compression works so well, it helps to know what JPEG compression actually does under the surface. It’s not just “make the file smaller” — it’s a multi-stage pipeline that exploits how human vision works. JPEG compression works in five distinct stages: Color space conversion (RGB → YCbCr). Your image starts as red, green, and blue channels. The encoder converts it into luminance (brightness) and two chrominance (color) channels. This separation is key — human eyes are far more sensitive to brightness than to color. Chroma subsampling. Since our eyes barely notice color detail, the encoder reduces the resolution of the two color channels. A common scheme is 4:2:0, which halves the color resolution in both dimensions — cutting color data to 25% of its original size with almost no perceptible difference. Discrete Cosine Transform (DCT) on 8×8 pixel blocks. The image is divided into 8×8 pixel blocks, and each block is transformed from spatial data (pixel values) into frequency data (patterns of light and dark). Low-frequency components represent smooth gradients; high-frequency components represent sharp edges and fine detail. Quantization — this is where quality loss happens. The frequency data from each block is divided by a quantization matrix and rounded. High-frequency components (fine detail) get divided by larger numbers, effectively zeroing them out. This is the lossy step — and it’s where the quality parameter has its effect. Huffman encoding. Finally, the quantized data is compressed using lossless Huffman coding, which replaces common patterns with shorter bit sequences. This is the same principle behind ZIP compression — no data is lost in this step. When you call canvas.toBlob() with a quality parameter, the browser’s built-in JPEG encoder runs all of these steps. The quality parameter (0.0 to 1.0) controls step 4 — quantization. Lower quality means more aggressive quantization, which produces a smaller file but introduces more artifacts. Higher quality preserves more detail but results in a larger file. Here’s how the browser’s compression maps to these steps in practice: // The browser handles all the complexity behind this one call canvas.toBlob( (blob) => { // blob is your compressed image console.log(`Compressed: ${blob.size} bytes`); }, 'image/jpeg', 0.8 // quality: 0.0 (max compression) to 1.0 (no compression) ); That single method call triggers the entire five-stage pipeline. The browser’s native JPEG encoder — written in optimized C++ and compiled to machine code — handles color conversion, DCT transforms, quantization, and Huffman coding. You get the output of a sophisticated compression algorithm through one line of JavaScript. The Complete Compression Pipeline: File → Canvas → Blob → Download Understanding the theory is useful, but let’s look at how the full pipeline works in practice. Here’s the complete compression function that QuickShrink uses at its core: async function compressImage(file, quality = 0.8) { // Step 1: Read file into an Image object const img = await createImageBitmap(file); // Step 2: Create canvas at original dimensions const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; // Step 3: Draw image onto canvas const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); // Step 4: Re-encode as JPEG at target quality const blob = await new Promise(resolve => { canvas.toBlob(resolve, 'image/jpeg', quality); }); // Step 5: Calculate savings const savings = ((file.size - blob.size) / file.size * 100).toFixed(1); console.log(`${file.name}: ${formatBytes(file.size)} → ${formatBytes(blob.size)} (${savings}% saved)`); return blob; } function formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } The download trigger is equally straightforward — we create a temporary object URL, simulate a click on an anchor element, and immediately clean up: function downloadBlob(blob, filename) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename.replace(/\.[^.]+$/, '_compressed.jpg'); document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); // free memory } The entire pipeline — from file selection to download — happens in under 500ms for a typical 3MB photo. No network round-trips, no upload progress bars, no waiting for a server to process your image. The bottleneck is your CPU’s JPEG encoder, which on any modern device is blazingly fast. EXIF Data: The Privacy Metadata You Forgot About Every photo your phone takes is embedded with invisible metadata called EXIF data. This includes GPS coordinates (often accurate to within a few meters), your camera model and serial number, the exact timestamp the photo was taken, and even the software used to edit it. If you’ve ever wondered how someone could figure out where a photo was taken — EXIF is the answer. The amount of data stored in EXIF is staggering. A typical iPhone photo contains over 40 metadata fields: latitude and longitude, altitude, lens aperture, shutter speed, ISO, focal length, white balance, flash status, orientation, color space, and device-specific identifiers. Some Android phones even include the device’s unique hardware ID. When you share that photo — compressed or not — all of that metadata travels with it unless explicitly removed. Here’s the problem: most “compress” tools keep EXIF data intact. Your compressed image still broadcasts your location, your device information, and your editing history. You think you’re just making a file smaller, but you’


---
## Secure Remote Access for Your Homelab

- URL: https://orthogonal.info/secure-remote-access-for-your-homelab/
- Date: 2026-03-22
- Category: Homelab
- Summary: Set up secure remote access for your homelab using enterprise-grade practices. Covers VPN, SSH hardening, reverse proxies, and zero trust architecture.

I manage my homelab remotely every day—30+ Docker containers on TrueNAS SCALE, accessed from coffee shops, airports, and hotel Wi-Fi. After finding brute-force attempts in my logs within hours of opening SSH to the internet, I locked everything down. Here’s exactly how I secure remote access now. Introduction to Secure Remote Access 📌 TL;DR: Learn how to adapt enterprise-grade security practices for safe and efficient remote access to your homelab, ensuring strong protection against modern threats. Introduction to Secure Remote Access Picture this: You’ve spent weeks meticulously setting up your homelab. 🎯 Quick Answer: Secure remote homelab access using WireGuard VPN with mTLS, OPNsense firewall rules, and Crowdsec intrusion prevention. This setup safely manages 30+ Docker containers remotely while blocking unauthorized access at multiple layers. 🏠 My setup: TrueNAS SCALE · 64GB ECC RAM · dual 10GbE NICs · WireGuard VPN on OPNsense · Authelia for SSO · all services behind reverse proxy with TLS. Picture this: You’ve spent weeks meticulously setting up your homelab. Virtual machines are humming, your Kubernetes cluster is running smoothly, and you’ve finally configured that self-hosted media server you’ve been dreaming about. Then, you decide to access it remotely while traveling, only to realize your setup is wide open to the internet. A few days later, you notice strange activity on your server logs—someone has brute-forced their way in. The dream has turned into a nightmare. Remote access is a cornerstone of homelab setups. Whether you’re managing virtual machines, hosting services, or experimenting with new technologies, the ability to securely access your resources from anywhere is invaluable. However, unsecured remote access can leave your homelab vulnerable to attacks, ranging from brute force attempts to more sophisticated exploits. we’ll explore how you can scale down enterprise-grade security practices to protect your homelab. The goal is to strike a balance between strong security and practical usability, ensuring your setup is safe without becoming a chore to manage. Homelabs are often a playground for tech enthusiasts, but they can also serve as critical infrastructure for personal or small business projects. This makes securing remote access even more important. Attackers often target low-hanging fruit, and an unsecured homelab can quickly become a victim of ransomware, cryptojacking, or data theft. By implementing the strategies outlined you’ll not only protect your homelab but also gain valuable experience in cybersecurity practices that can be applied to larger-scale environments. Whether you’re a beginner or an experienced sysadmin, there’s something here for everyone. 💡 Pro Tip: Always start with a security audit of your homelab. Identify services exposed to the internet and prioritize securing those first. Key Principles of Enterprise Security Before diving into the technical details, let’s talk about the foundational principles of enterprise security and how they apply to homelabs. These practices might sound intimidating, but they’re surprisingly adaptable to smaller-scale environments. Zero Trust Architecture Zero Trust is a security model that assumes no user or device is trustworthy by default, even if they’re inside your network. Every access request is verified, and permissions are granted based on strict policies. For homelabs, this means implementing controls like authentication, authorization, and network segmentation to ensure only trusted users and devices can access your resources. For example, you can use VLANs (Virtual LANs) to segment your network into isolated zones. This prevents devices in one zone from accessing resources in another zone unless explicitly allowed. Combine this with strict firewall rules to enforce access policies. Another practical application of Zero Trust is to use role-based access control (RBAC). Assign specific permissions to users based on their roles. For instance, your media server might only be accessible to family members, while your Kubernetes cluster is restricted to your personal devices. Multi-Factor Authentication (MFA) MFA is a simple yet powerful way to secure remote access. By requiring a second form of verification—like a one-time code from an app or hardware token—you add an additional layer of security that makes it significantly harder for attackers to gain access, even if they manage to steal your password. Consider using apps like Google Authenticator or Authy for MFA. For homelabs, you can integrate MFA with services like SSH, VPNs, or web applications using tools like Authelia or Duo. These tools are lightweight and easy to configure for personal use. Hardware-based MFA, such as YubiKeys, offers even greater security. These devices generate one-time codes or act as physical keys that must be present to authenticate. They’re particularly useful for securing critical services like SSH or admin dashboards. Encryption and Secure Tunneling Encryption ensures that data transmitted between your device and homelab is unreadable to anyone who intercepts it. Secure tunneling protocols like WireGuard or OpenVPN create encrypted channels for remote access, protecting your data from prying eyes. For example, WireGuard is known for its simplicity and performance. It uses modern cryptographic algorithms to establish secure connections quickly. Here’s a sample configuration for a WireGuard client: # WireGuard client configuration [Interface] PrivateKey = <client-private-key> Address = 10.0.0.2/24 [Peer] PublicKey = <server-public-key> Endpoint = your-homelab-ip:51820 AllowedIPs = 0.0.0.0/0 By using encryption and secure tunneling, you can safely access your homelab even on public Wi-Fi networks. 💡 Pro Tip: Always use strong encryption algorithms like AES-256 or ChaCha20 for secure communications. Avoid outdated protocols like PPTP. ⚠️ What went wrong for me: I once left an SSH port exposed with password auth “just for testing.” Within 6 hours, my Wazuh dashboard lit up with thousands of brute-force attempts from IPs across three continents. I immediately switched to key-only auth and moved SSH behind my WireGuard VPN. Now nothing is directly exposed to the internet—every service goes through the tunnel. Practical Patterns for Homelab Security Now that we’ve covered the principles, let’s get into practical implementations. These are tried-and-true methods that can significantly improve the security of your homelab without requiring enterprise-level budgets or infrastructure. Using VPNs for Secure Access A VPN (Virtual Private Network) allows you to securely connect to your homelab as if you were on the local network. Tools like WireGuard are lightweight, fast, and easy to set up. Here’s a basic WireGuard configuration: # Install WireGuard sudo apt update && sudo apt install wireguard # Generate keys wg genkey | tee privatekey | wg pubkey > publickey # Configure the server sudo nano /etc/wireguard/wg0.conf # Example configuration [Interface] PrivateKey = <your-private-key> Address = 10.0.0.1/24 ListenPort = 51820 [Peer] PublicKey = <client-public-key> AllowedIPs = 10.0.0.2/32 Once configured, you can connect securely to your homelab from anywhere. VPNs are particularly useful for accessing services that don’t natively support encryption or authentication. By routing all traffic through a secure tunnel, you can protect even legacy applications. 💡 Pro Tip: Use dynamic DNS services like DuckDNS or No-IP to maintain access to your homelab even if your public IP changes. Setting Up SSH with Public Key Authentication SSH is a staple for remote access, but using passwords is a recipe for disaster. Public key authentication is far more secure. Here’s how you can set it up: # Generate SSH keys on your local machine ssh-keygen -t rsa -b 4096 -C "[email protected]" # Copy the public key to your homelab server ssh-copy-id user@homelab-ip # Disable password authentication for SSH sudo nano /e


---
## Home Network Segmentation with OPNsense

- URL: https://orthogonal.info/home-network-segmentation-with-opnsense/
- Date: 2026-03-20
- Category: Homelab
- Summary: Segment your home network with OPNsense for enterprise-grade security. Covers VLANs, firewall rules, IoT isolation, and inter-zone traffic policies.

My homelab runs 30+ Docker containers on TrueNAS SCALE. Without network segmentation, every one of them could talk to every device in my house—including IoT cameras, guest phones, and my kids’ tablets. Here’s the OPNsense configuration that keeps them properly isolated. Introduction to Network Segmentation 📌 TL;DR: Learn how to apply enterprise-grade network segmentation practices to your homelab using OPNsense, enhancing security and minimizing risks. 🎯 Quick Answer: Segment your home network with OPNsense by creating dedicated VLANs for IoT, servers, management, and guest devices. This isolates 30+ Docker containers so a compromised IoT device cannot reach your NAS or management interfaces. 🏠 My setup: TrueNAS SCALE · 64GB ECC RAM · dual 10GbE NICs · OPNsense on a Protectli vault · 4 VLANs (IoT, Trusted, DMZ, Guest) · 30+ Docker containers · 60TB+ ZFS storage. Picture this: you’re troubleshooting a slow internet connection at home, only to discover that your smart fridge is inexplicably trying to communicate with your NAS. If that sounds absurd, welcome to the chaotic world of unsegmented home networks. Without proper segmentation, every device in your network can talk to every other device, creating a sprawling attack surface ripe for exploitation. Network segmentation is the practice of dividing a network into smaller, isolated segments to improve security, performance, and manageability. In enterprise environments, segmentation is a cornerstone of security architecture, but it’s just as critical for home networks—especially if you’re running a homelab or hosting sensitive data. Enter OPNsense, a powerful open-source firewall and routing platform. With its robust feature set, including support for VLANs, advanced firewall rules (and be sure to keep your firewall management interfaces patched and isolated), and traffic monitoring, OPNsense is the perfect tool to bring enterprise-grade network segmentation to your home. Segmentation not only reduces the risk of cyberattacks but also improves network performance by limiting unnecessary traffic between devices. For example, your NAS doesn’t need to communicate with your smart light bulbs, and your work laptop shouldn’t be exposed to traffic from your gaming console. By isolating devices into logical groups, you ensure that each segment operates independently, reducing congestion and enhancing overall network efficiency. Another key benefit of segmentation is simplified troubleshooting. Imagine a scenario where your network experiences a sudden slowdown. If your devices are segmented, you can quickly identify which VLAN is causing the issue and narrow down the problematic device or service. This is particularly useful in homelabs, where experimental setups can occasionally introduce instability. 💡 Pro Tip: Use OPNsense’s built-in traffic monitoring tools to visualize data flow between segments and pinpoint bottlenecks or anomalies. Enterprise Security Principles for Home Use When adapting enterprise security principles to a homelab, the goal is to minimize risks while maintaining functionality. One of the most effective strategies is implementing a zero-trust model. In a zero-trust environment, no device is trusted by default—even if it’s inside your network perimeter. Every device must prove its identity and adhere to strict access controls. VLANs (Virtual Local Area Networks) are the backbone of network segmentation. Think of VLANs as virtual fences that separate devices into distinct zones. For example, you can create one VLAN for IoT devices, another for your workstations, and a third for your homelab servers. This separation reduces the risk of lateral movement—where an attacker compromises one device and uses it to pivot to others. ⚠️ Security Note: IoT devices are notorious for weak security. Segmentation ensures that a compromised smart device can’t access your critical systems. By segmenting your home network, you’re effectively shrinking your attack surface. Even if one segment is breached, the damage is contained, and other parts of your network remain secure. Another enterprise principle worth adopting is the principle of least privilege. This means granting devices and users only the minimum access required to perform their tasks. For instance, your smart thermostat doesn’t need access to your NAS or homelab servers. By applying strict firewall rules and access controls, you can enforce this principle and further reduce the risk of unauthorized access. Consider real-world scenarios like a guest visiting your home and connecting their laptop to your Wi-Fi. Without segmentation, their device could potentially access your internal systems, posing a security risk. With proper VLAN configuration, you can isolate guest devices into a dedicated segment, ensuring they only have internet access and nothing more. 💡 Pro Tip: Use OPNsense’s captive portal feature to add an extra layer of security to your guest network, requiring authentication before granting access. ⚠️ What went wrong for me: When I first segmented my network, my Chromecast couldn’t discover media servers across VLANs. Streaming just stopped working. The fix? Enabling mDNS reflection in OPNsense under Services → mDNS Repeater. It took me an embarrassing two hours to figure out, but now service discovery works seamlessly across my Trusted and IoT VLANs. Setting Up OPNsense for Network Segmentation Now that we understand the importance of segmentation, let’s dive into the practical steps of setting up OPNsense. The process involves configuring VLANs, assigning devices to the appropriate segments, and creating firewall rules to enforce isolation. Initial Configuration Start by logging into your OPNsense web interface. Navigate to Interfaces → Assignments and create new VLANs for your network segments. For example: # Example VLAN setup vlan10 - IoT devices vlan20 - Workstations vlan30 - Homelab servers Once the VLANs are created, assign them to physical network interfaces or virtual interfaces if you’re using a managed switch. After assigning VLANs, configure DHCP servers for each VLAN under Services → DHCP Server. This ensures that devices in each segment receive IP addresses within their respective ranges. For example: # Example DHCP configuration VLAN10: 192.168.10.0/24 VLAN20: 192.168.20.0/24 VLAN30: 192.168.30.0/24 Creating Firewall Rules Next, configure firewall rules to enforce isolation between VLANs. For example, you might want to block all traffic between your IoT VLAN and your workstation VLAN: # Example firewall rule Action: Block Source: VLAN10 (IoT) Destination: VLAN20 (Workstations) Don’t forget to allow necessary traffic, such as DNS and DHCP, between VLANs and your router. Misconfigured rules can lead to connectivity issues. 💡 Pro Tip: Test your firewall rules with a tool like ping or traceroute to ensure devices are properly isolated. One common pitfall during configuration is forgetting to allow management access to OPNsense itself. If you block all traffic from a VLAN, you may inadvertently lock yourself out of the web interface. To avoid this, create a rule that allows access to the OPNsense management IP from all VLANs. ⚠️ Warning: Always double-check your firewall rules before applying them to avoid accidental lockouts. Use Cases for Home Network Segmentation Network segmentation isn’t just a theoretical exercise—it has practical applications that can significantly improve your home network’s security and usability. Here are some common use cases: Separating IoT Devices IoT devices, such as smart thermostats and cameras, are often riddled with vulnerabilities. By placing them in a dedicated VLAN, you can prevent them from accessing sensitive systems like your NAS or workstations. For example, if a vulnerability in your smart camera is exploited, the attacker would be confined to the IoT VLAN, unable to access your homelab or personal devices. This segmentation acts as a safety net, reducing the impact of potenti


---
## Open Source Security Monitoring for Developers

- URL: https://orthogonal.info/open-source-security-monitoring-for-developers/
- Date: 2026-03-20
- Category: Security
- Summary: Discover open source security monitoring tools for developers. Bridge the gap between engineering and security with practical, self-hosted solutions.

I run Wazuh SIEM, OPNsense, and Suricata IDS on my own homelab — the same open source stack I’ve deployed in production environments. Most developers think security monitoring is someone else’s job. After 12 years of watching incidents unfold because the dev team had zero visibility, I’m here to tell you: if you can read logs, you can do security monitoring. Here’s how to start. Why Security Monitoring Shouldn’t Be Just for Security Teams 📌 TL;DR: Discover how open source tools can help developers to take charge of security monitoring, bridging the gap between engineering and security teams. Why Security Monitoring Shouldn’t Be Just for Security Teams The error logs were a mess. 🎯 Quick Answer: Deploy open-source security monitoring using Wazuh SIEM for log analysis, OPNsense for firewall visibility, and Suricata IDS for network intrusion detection. This stack gives developers enterprise-grade threat detection at zero licensing cost. The error logs were a mess. Suspicious traffic was flooding the application, but nobody noticed until it was too late. The security team was scrambling to contain the breach, while developers were left wondering how they missed the early warning signs. Sound familiar? For years, security monitoring has been treated as the exclusive domain of security teams. Developers write code, security teams monitor threats—end of story. But this divide is a recipe for disaster. When developers lack visibility into security issues, vulnerabilities can linger undetected until they explode in production. Security monitoring needs to shift left. Developers should be empowered to identify and address security risks early in the development lifecycle. Open source tools are a big improvement here, offering accessible and customizable solutions that bridge the gap between engineering and security teams. Consider a scenario where a developer introduces a new API endpoint but fails to implement proper authentication. Without security monitoring in place, this vulnerability could go unnoticed until attackers exploit it. However, with tools like Wazuh or OSSEC, developers could receive alerts about unusual access patterns or failed authentication attempts, enabling them to act swiftly. Another example is the rise of supply chain attacks, where malicious code is injected into dependencies. Developers who rely solely on security teams might miss these threats until their applications are compromised. By integrating security monitoring tools into their workflows, developers can detect anomalies in dependency behavior early on. 💡 Pro Tip: Educate your team about common attack vectors like SQL injection, cross-site scripting (XSS), and privilege escalation. Awareness is the first step toward effective monitoring. When developers and security teams collaborate, the result is a more resilient application. Developers bring deep knowledge of the codebase, while security teams provide expertise in threat detection. Together, they can create a solid security monitoring strategy that catches issues before they escalate. Key Open Source Tools for Security Monitoring Open source tools have democratized security monitoring, making it easier for developers to integrate security into their workflows. Here are some standout options: OSSEC: A powerful intrusion detection system that monitors logs, file integrity, and system activity. It’s lightweight and developer-friendly. Wazuh: Built on OSSEC, Wazuh adds a modern interface and enhanced capabilities like vulnerability detection and compliance monitoring. Zeek: Formerly known as Bro, Zeek is a network monitoring tool that excels at analyzing traffic for anomalies and threats. ClamAV: An open source antivirus engine that can scan files for malware, making it ideal for CI/CD pipelines and file storage systems. These tools integrate smoothly with developer workflows. For example, Wazuh can send alerts to Slack or email, ensuring developers stay informed without needing to sift through endless logs. Zeek can be paired with dashboards like Kibana for real-time traffic analysis. ClamAV can be automated to scan uploaded files in web applications, providing an additional layer of security. # Example: Running ClamAV to scan a directory clamscan -r /path/to/directory Real-world examples highlight the effectiveness of these tools. A fintech startup used Zeek to monitor API traffic, identifying and blocking a botnet attempting credential stuffing attacks. Another team implemented OSSEC to monitor file integrity on their servers, catching unauthorized changes to critical configuration files. 💡 Pro Tip: Regularly update your open source tools to ensure you have the latest security patches and features. While these tools are powerful, they require proper configuration to be effective. Spend time understanding their capabilities and tailoring them to your specific use case. For instance, Wazuh’s compliance monitoring can be customized to meet industry-specific standards like PCI DSS or HIPAA. Setting Up Security Monitoring as a Developer 🔍 Lesson learned: When I first set up Wazuh on my homelab, I made the classic mistake of enabling every rule. I was drowning in 10,000+ alerts per day — most of them noise. The real skill isn’t deploying the tool; it’s tuning it. Start with 5-10 high-fidelity rules (failed SSH, privilege escalation, file integrity changes) and expand from there. Getting started with open source security monitoring doesn’t have to be overwhelming. Here’s a step-by-step guide to deploying a tool like Wazuh: Install the tool: Use Docker or a package manager to set up the software. For Wazuh, you can use the official Docker images. Configure agents: Install agents on your servers or containers to collect logs and metrics. Set up alerts: Define rules for triggering alerts based on suspicious activity. Visualize data: Integrate with dashboards like Kibana for actionable insights. # Example: Deploying Wazuh with Docker docker run -d --name wazuh-manager -p 55000:55000 -p 1514:1514/udp wazuh/wazuh docker run -d --name wazuh-dashboard -p 5601:5601 wazuh/wazuh-dashboard Configuring alerts and dashboards is where the magic happens. Focus on actionable insights—alerts should tell you what’s wrong and how to fix it, not just flood your inbox with noise. For example, you might configure Wazuh to alert you when it detects multiple failed login attempts within a short time frame. This could indicate a brute force attack. Similarly, Zeek can be set up to flag unusual DNS queries, which might signal command-and-control communication from malware. ⚠️ Security Note: Always secure your monitoring tools. Exposing dashboards or agents to the internet without proper authentication is asking for trouble. Common pitfalls include overloading your system with unnecessary rules or failing to test alerts. Start with a few critical rules and refine them over time based on real-world feedback. Regularly review and update your configurations to adapt to evolving threats. Building a Security-First Culture in Development Teams Security monitoring tools are only as effective as the people using them. To truly integrate security into development, you need a culture shift. Encourage collaboration between developers and security teams. Host joint training sessions where developers learn to interpret security monitoring data. Use real-world examples to show how early detection can prevent costly incidents. Promote shared responsibility for security. Developers should feel empowered to act on security alerts, not just pass them off to another team. Tools like Wazuh and Zeek make this easier by providing clear, actionable insights. One effective strategy is to integrate security metrics into team performance reviews. For example, track the number of vulnerabilities identified and resolved during development. Celebrate successes to reinforce the importance of security. 💡 Pro Tip: Gamify security monitoring. Reward developers who identify and fix vulner


---
## Kubernetes Secrets Management: A Security-First Guide

- URL: https://orthogonal.info/kubernetes-secrets-management-a-security-first-guide/
- Date: 2026-03-19
- Category: DevOps
- Summary: Most Kubernetes secrets are dangerously insecure by default. Learn sealed secrets, external vaults, RBAC policies, and encryption-at-rest best practices.

I’ve lost count of how many clusters I’ve audited where secrets were stored as plain base64 in etcd — which is encoding, not encryption. After cleaning up secrets sprawl across enterprise clusters for years, I can tell you: most teams don’t realize how exposed they are until it’s too late. Here’s the guide I wish I’d had when I started. Introduction to Secrets Management in Kubernetes 📌 TL;DR: Introduction to Secrets Management in Kubernetes Most Kubernetes secrets management practices are dangerously insecure. If you’ve been relying on Kubernetes native secrets without additional safeguards, you’re gambling with your sensitive data. 🎯 Quick Answer: Kubernetes Secrets are base64-encoded, not encrypted, making them readable by anyone with etcd or API access. Use External Secrets Operator with HashiCorp Vault or AWS Secrets Manager, enable etcd encryption at rest, and enforce RBAC to restrict Secret access in production clusters. Most Kubernetes secrets management practices are dangerously insecure. If you’ve been relying on Kubernetes native secrets without additional safeguards, you’re gambling with your sensitive data. Kubernetes makes it easy to store secrets, but convenience often comes at the cost of security. Secrets management is a cornerstone of secure Kubernetes environments. Whether it’s API keys, database credentials, or TLS certificates, these sensitive pieces of data are the lifeblood of your applications. Unfortunately, Kubernetes native secrets are stored in plaintext within etcd, which means anyone with access to your cluster’s etcd database can potentially read them. To make matters worse, most teams don’t encrypt their secrets at rest or rotate them regularly. This creates a ticking time bomb for security incidents. Thankfully, tools like HashiCorp Vault and External Secrets provide robust solutions to these challenges, enabling you to adopt a security-first approach to secrets management. Another key concern is the lack of granular access controls in Kubernetes native secrets. By default, secrets can be accessed by any pod in the namespace unless additional restrictions are applied. This opens the door to accidental or malicious exposure of sensitive data. Teams must implement strict role-based access controls (RBAC) and namespace isolation to mitigate these risks. Consider a scenario where a developer accidentally deploys an application with overly permissive RBAC rules. If the application is compromised, the attacker could gain access to all secrets in the namespace. This highlights the importance of adopting tools that enforce security best practices automatically. 💡 Pro Tip: Always audit your Kubernetes RBAC configurations to ensure that only the necessary pods and users have access to secrets. Use tools like kube-bench or kube-hunter to identify misconfigurations. To get started with secure secrets management, teams should evaluate their current practices and identify gaps. Are secrets encrypted at rest? Are they rotated regularly? Are access logs being monitored? Answering these questions is the first step toward building a solid secrets management strategy. Vault: A Deep Dive into Secure Secrets Management 🔍 Lesson learned: During a production migration, we discovered that 40% of our Kubernetes secrets hadn’t been rotated in over a year — some contained credentials for services that no longer existed. I now enforce automatic rotation policies from day one. Vault’s lease-based secrets solved this completely for our database credentials. HashiCorp Vault is the gold standard for secrets management. It’s designed to securely store, access, and manage sensitive data. Unlike Kubernetes native secrets, Vault encrypts secrets at rest and provides fine-grained access controls, audit logging, and dynamic secrets generation. Vault integrates smoothly with Kubernetes, allowing you to securely inject secrets into your pods without exposing them in plaintext. Here’s how Vault works: Encryption: Vault encrypts secrets using AES-256 encryption before storing them. Dynamic Secrets: Vault can generate secrets on demand, such as temporary database credentials, reducing the risk of exposure. Access Policies: Vault uses policies to control who can access specific secrets. Setting up Vault for Kubernetes integration involves deploying the Vault agent injector. This agent automatically injects secrets into your pods as environment variables or files. Below is an example configuration: apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: template: metadata: annotations: vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: "my-app-role" vault.hashicorp.com/agent-inject-secret-config: "secret/data/my-app/config" spec: containers: - name: my-app image: my-app:latest In this example, Vault injects the secret stored at secret/data/my-app/config into the pod. The vault.hashicorp.com/role annotation specifies the Vault role that governs access to the secret. Another powerful feature of Vault is its ability to generate dynamic secrets. For example, Vault can create temporary database credentials that automatically expire after a specified duration. This reduces the risk of long-lived credentials being compromised. Here’s an example of a dynamic secret policy: path "database/creds/my-role" { capabilities = ["read"] } Using this policy, Vault can generate database credentials for the my-role role. These credentials are time-bound and automatically revoked after their lease expires. 💡 Pro Tip: Use Vault’s dynamic secrets for high-risk systems like databases and cloud services. This minimizes the impact of credential leaks. Common pitfalls when using Vault include misconfigured policies and insufficient monitoring. Always test your Vault setup in a staging environment before deploying to production. Also, enable audit logging to track access to secrets and identify suspicious activity. External Secrets: Simplifying Secrets Synchronization ⚠️ Tradeoff: External Secrets Operator adds a sync layer between your secrets store and Kubernetes. That’s another component that can fail — and when it does, pods can’t start. I run it with high availability and aggressive health checks. The operational overhead is real, but it beats manually syncing secrets across 20 namespaces. While Vault excels at secure storage, managing secrets across multiple environments can still be a challenge. This is where External Secrets comes in. External Secrets is an open-source Kubernetes operator that synchronizes secrets from external secret stores like Vault, AWS Secrets Manager, or Google Secret Manager into Kubernetes secrets. External Secrets simplifies the process of keeping secrets up-to-date in Kubernetes. It dynamically syncs secrets from your external store, ensuring that your applications always have access to the latest credentials. Here’s an example configuration: apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: my-app-secrets spec: refreshInterval: "1h" secretStoreRef: name: vault-backend kind: SecretStore target: name: my-app-secrets creationPolicy: Owner data: - secretKey: config remoteRef: key: secret/data/my-app/config In this example, External Secrets fetches the secret from Vault and creates a Kubernetes secret named my-app-secrets. The refreshInterval ensures that the secret is updated every hour. Real-world use cases for External Secrets include managing API keys for third-party services or synchronizing database credentials across multiple clusters. By automating secret updates, External Secrets reduces the operational overhead of managing secrets manually. One challenge with External Secrets is handling failures during synchronization. If the external secret store becomes unavailable, applications may lose access to critical secrets. To mitigate this, configure fallback mechanisms or cache secrets locally. ⚠️ Warning: Always monitor the health of your external secret store. Use tools like Prometheus or Grafana 


---
## AI Market Signals: What Stock Trends Say This Week

- URL: https://orthogonal.info/this-week-in-markets-what-ai-research-tells-us-about-the-war-crisis-rotation/
- Date: 2026-03-14
- Category: Finance &amp; Trading
- Summary: Our AI narrative detection shifted from MIXED to WAR_CRISIS this week. Oil surged 59%, geopolitical risk hit 91.2/100, and the rotation signals are screaming: defense, energy, gold in — tech out.

The week ending March 14, 2026 was defined by one word: crisis. Our AI-driven narrative detection system has officially shifted from a MIXED regime to WAR_CRISIS dominance — and the data behind that shift tells a compelling story about where money is moving next. The Narrative Shift 📌 TL;DR: The week ending March 14, 2026 was defined by one word: crisis . Our AI-driven narrative detection system has officially shifted from a MIXED regime to WAR_CRISIS dominance — and the data behind that shift tells a compelling story about where money is moving next. 🎯 Quick Answer: AI narrative detection identified a dominant shift to WAR_CRISIS sentiment the week of March 14, 2026, signaling defense sector momentum and risk-off rotation. Traders should monitor narrative regime changes as leading indicators before price action confirms the trend. Our proprietary narrative scoring engine tracks six major market narratives in real-time, weighting news flow, price action, and cross-asset signals. Here’s where things stand this week: NarrativeScoreDirectionWAR_CRISIS55.8⬆️ DominantAI_BOOM37.0⬇️ FadingRATE_CUT_HOPE3.2➡️ DeadINFLATION_SHOCK1.9⬆️ WatchRECESSION_FEAR1.9➡️ Quiet The transition from MIXED to WAR_CRISIS happened mid-week with 69% confidence — a significant regime change that reshuffles everything from sector allocations to risk budgets. The Geopolitical Picture: Extreme Risk Our macro/geopolitical module is flashing its highest reading in months: Geopolitical Risk Score: 91.2/100 — classified as EXTREME Oil: +59.2% in 30 days, trend rising Dollar: Strengthening (flight to safety) Treasury Yields: Rising (inflation expectations baked in) Oil-Equity Correlation: -0.65 (strongly negative — oil up = stocks down) This combination — surging oil, rising yields, and extreme geopolitical stress — creates a toxic backdrop for rate-sensitive and growth-heavy portfolios. Where to Rotate: AI-Driven Sector Calls Favored Sectors: 🛡️ Defense (LMT, RTX, NOC, GD) — Direct geopolitical beneficiaries ⛽ Energy (XOM, CVX) — Oil surge = earnings windfall 🥇 Gold (GLD) — Classic crisis hedge ⚡ Utilities — Defensive yield plays Sectors to Avoid: 💻 Tech (AAPL, MSFT, GOOGL) — Rising yields compress PE multiples 🛍️ Consumer Discretionary — Oil squeeze hits consumer wallets 🏠 Real Estate — Rate-sensitive, no safe harbor 🚗 TSLA — Growth premium at risk in this regime Building an AI Signal Scanner in Python I don’t trust narratives — I trust code. The signal scanner behind these weekly reports is a Python script that combines multiple technical indicators into a composite score. Here’s the core of what runs every morning on my homelab server: import pandas as pd import yfinance as yf import numpy as np def fetch_signals(ticker, period='6mo'): """Fetch price data and calculate technical signals.""" df = yf.download(ticker, period=period, progress=False) # Moving averages df['SMA_20'] = df['Close'].rolling(20).mean() df['SMA_50'] = df['Close'].rolling(50).mean() df['EMA_12'] = df['Close'].ewm(span=12).mean() df['EMA_26'] = df['Close'].ewm(span=26).mean() # RSI (Relative Strength Index) delta = df['Close'].diff() gain = delta.where(delta > 0, 0).rolling(14).mean() loss = (-delta.where(delta < 0, 0)).rolling(14).mean() rs = gain / loss df['RSI'] = 100 - (100 / (1 + rs)) # MACD df['MACD'] = df['EMA_12'] - df['EMA_26'] df['MACD_Signal'] = df['MACD'].ewm(span=9).mean() # Volume trend df['Vol_SMA'] = df['Volume'].rolling(20).mean() df['Vol_Ratio'] = df['Volume'] / df['Vol_SMA'] return df def composite_score(df): """Combine indicators into a single -100 to +100 score.""" latest = df.iloc[-1] score = 0 # Trend: SMA crossover (+/- 30 points) if latest['SMA_20'] > latest['SMA_50']: score += 30 else: score -= 30 # Momentum: RSI zone (+/- 25 points) if latest['RSI'] > 70: score -= 25 # overbought elif latest['RSI'] < 30: score += 25 # oversold else: score += (latest['RSI'] - 50) * 0.5 # MACD crossover (+/- 25 points) if latest['MACD'] > latest['MACD_Signal']: score += 25 else: score -= 25 # Volume confirmation (+/- 20 points) if latest['Vol_Ratio'] > 1.5: score += 20 if score > 0 else -20 return round(score, 1) # Scan a watchlist tickers = ['XOM', 'LMT', 'GLD', 'AAPL', 'RTX', 'CVX'] for t in tickers: df = fetch_signals(t) sc = composite_score(df) direction = 'BULLISH' if sc > 20 else 'BEARISH' if sc < -20 else 'NEUTRAL' print(f'{t}: {sc:+.1f} ({direction})') The composite score combines four independent signals — trend, momentum, MACD divergence, and volume confirmation — into a single number between -100 and +100. Anything above +20 gets flagged as bullish; below -20, bearish. The volume confirmation acts as a multiplier: a trend signal without volume behind it gets discounted. How I Automate My Market Research I built a Python pipeline that runs every morning at 6 AM, scans 500 tickers, and sends me a summary before I’ve finished my coffee. The architecture is intentionally simple — no Kubernetes, no message queues, just a cron job on a TrueNAS box in my homelab. The pipeline has four stages: Data fetch — Pull 6 months of daily OHLCV data for each ticker using yfinance. I cache aggressively to avoid hitting rate limits; data older than 24 hours gets refreshed, everything else is served from a local SQLite database. Signal calculation — Run every ticker through the composite scoring function. This generates a ranked list sorted by absolute signal strength — I want to see the strongest convictions first, whether bullish or bearish. Regime detection — Cross-reference individual ticker signals with macro indicators (VIX level, yield curve slope, oil price momentum). If 70% or more of energy tickers are flashing bullish while tech is bearish, that’s a regime signal, not just individual noise. Notification — Format the top 20 signals and any regime changes into a summary, then push it to Telegram via the Bot API. Total cost: $0/month. Total infrastructure: one Python script and one cron entry. Why do I trust systematic signals over gut feelings? Because I backtested both. Over a 3-year historical window, the composite signal scanner had a 62% hit rate on 5-day forward returns — not spectacular, but significantly better than my intuition, which backtested at roughly coin-flip accuracy. The edge isn’t in any single indicator; it’s in the combination and the discipline of not overriding the system when it tells you something you don’t want to hear. The backtesting convinced me after I ran a simple simulation: $10,000 starting capital, buy when composite score exceeds +30, sell when it drops below -10, 1% position sizing. Over 3 years of historical data, the systematic approach returned 47% versus 12% for my discretionary trading over the same period. The difference wasn’t the winners — it was avoiding the losers. The system doesn’t hold onto positions out of hope. Signal Processing: From Noise to Actionable Data The biggest challenge in technical analysis isn’t calculating indicators — any library can do that. It’s filtering false signals. A moving average crossover happens dozens of times per year on any given ticker. Most of them are noise. The trick is building confirmation rules that increase signal quality without reducing it to zero. My approach uses three filters: 1. Minimum holding period. After a signal triggers, ignore all opposing signals for 5 trading days. This prevents whipsawing — the classic failure mode where you buy on a crossover, sell the next day when it reverses, and repeat until your account is drained by transaction costs. 2. Volume confirmation. A crossover without above-average volume is more likely noise than signal. I require volume to be at least 1.2x the 20-day average before acting on any indicator change. This single rule eliminated roughly 40% of false signals in my backtesting. 3. Multi-indicator agreement. No single indicator triggers a trade. I need at least two of the four components (trend, momentum, MACD, volume) to agree before the composite score crosses the action thr


---
## Kubernetes Security Checklist for Production (2026)

- URL: https://orthogonal.info/kubernetes-security-checklist-for-production-2026/
- Date: 2026-03-13
- Category: DevOps
- Summary: A comprehensive Kubernetes security checklist for production in 2026. Covers RBAC, network policies, image scanning, runtime security, and audit logging.

I’ve audited dozens of Kubernetes clusters over 12 years in Big Tech — from small dev clusters to 500-node production fleets. The same misconfigurations show up again and again. This checklist catches about 90% of the issues I find during security reviews. It distills the most critical security controls into ten actionable areas — use it as a baseline audit for any cluster running production workloads. 1. API Server Access Control 📌 TL;DR: Securing a Kubernetes cluster in production requires a layered, defense-in-depth approach. Misconfigurations remain the leading cause of container breaches, and the attack surface of a default Kubernetes installation is far broader than most teams realize. 🎯 Quick Answer: Misconfigurations cause the majority of Kubernetes container breaches. A structured security checklist covering RBAC, network policies, pod security standards, and image scanning catches approximately 90% of issues typically found in professional security reviews. The Kubernetes API server is the front door to your cluster. Every request — from kubectl commands to controller reconciliation loops — passes through it. Weak access controls here compromise everything downstream. Enforce least-privilege RBAC. Audit every ClusterRoleBinding and RoleBinding. Remove default bindings that grant broad access. Use namespace-scoped Role objects instead of ClusterRole wherever possible, and never bind cluster-admin to application service accounts. Enable audit logging. Configure the API server with an audit policy that captures at least Metadata-level events for all resources and RequestResponse-level events for secrets, RBAC objects, and authentication endpoints. Ship logs to an immutable store. Disable anonymous authentication. Set --anonymous-auth=false on the API server. Use short-lived bound service account tokens rather than long-lived static tokens or client certificates with multi-year expiry. 2. Network Policies 🔍 Lesson learned: On one of my first production cluster audits, I found every pod could talk to every other pod — including the metadata service. An attacker who compromised one container had free lateral movement across the entire cluster. Default-deny network policies would have stopped that cold. By default, every pod in a Kubernetes cluster can communicate with every other pod — across namespaces, without restriction. Network Policies are the primary mechanism for implementing microsegmentation. Apply default-deny ingress and egress in every namespace. Start with a blanket deny rule, then selectively allow required traffic. This inverts the model from “everything allowed unless blocked” to “everything blocked unless permitted.” Restrict pod-to-pod communication by label selector. Define policies allowing frontend pods to reach backend pods, backend to databases, and nothing else. Be explicit about port numbers — do not allow all TCP traffic when only port 5432 is needed. Use a CNI plugin that enforces policies reliably. Verify your chosen plugin (Calico, Cilium, Antrea) actively enforces both ingress and egress rules. Test enforcement by attempting blocked connections in a staging cluster. 3. Pod Security Standards ⚠️ Tradeoff: Enforcing restricted Pod Security Standards breaks a surprising number of Helm charts and legacy workloads. I’ve had to rebuild container images to fix hardcoded UID assumptions and remove privileged escalation flags. Budget time for this — it’s worth it, but it’s not free. Pod Security Standards (PSS) replace the deprecated PodSecurityPolicy API. They define three profiles — Privileged, Baseline, and Restricted — that control what security-sensitive fields a pod spec may contain. Enforce the Restricted profile for application workloads. The Restricted profile requires pods to drop all capabilities, run as non-root, use a read-only root filesystem, and disallow privilege escalation. Apply it via the pod-security.kubernetes.io/enforce: restricted namespace label. Use Baseline for system namespaces that need flexibility. Some infrastructure components (log collectors, CNI agents) legitimately need host networking or elevated capabilities. Apply Baseline to these namespaces but audit each exception individually. Run in warn and audit mode before enforcing. Before switching to enforce, use warn and audit modes first. This surfaces violations without breaking deployments, giving teams time to remediate. 4. Image Security Container images are the software supply chain’s last mile. A compromised or outdated image introduces vulnerabilities directly into your runtime environment. Scan every image in your CI/CD pipeline. Integrate Trivy, Grype, or Snyk into your build pipeline. Fail builds that contain critical or high-severity CVEs. Scan on a schedule — new vulnerabilities are discovered against existing images constantly. Require signed images and verify at admission. Use cosign (Sigstore) to sign images at build time, and deploy an admission controller (Kyverno or OPA Gatekeeper) that rejects any image without a valid signature. Pin images by digest, never use :latest. The :latest tag is mutable. Pin image references to immutable SHA256 digests (e.g., myapp@sha256:abc123...) so deployments are reproducible and auditable. 5. Secrets Management Kubernetes Secrets are base64-encoded by default — not encrypted. Anyone with read access to the API server or etcd can trivially decode them. Mature secret management requires layers beyond the built-in primitives. Use an external secrets manager. Integrate with HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, or GCP Secret Manager via the External Secrets Operator or the Secrets Store CSI Driver. This keeps secret material out of etcd entirely. Enable encryption at rest for etcd. Configure --encryption-provider-config with an EncryptionConfiguration using aescbc, aesgcm, or a KMS provider. Verify by reading a secret directly from etcd to confirm ciphertext. Rotate secrets automatically. Never share secrets across namespaces. Use short TTLs where possible (e.g., Vault dynamic secrets), and automate rotation so leaked credentials expire before exploitation. 6. Logging and Monitoring You cannot secure what you cannot see. Complete observability transforms security from reactive incident response into proactive threat detection. Centralize Kubernetes audit logs. Forward API server audit logs to a SIEM or log aggregation platform (ELK, Loki, Splunk). Alert on suspicious patterns: privilege escalation attempts, unexpected secret access, and exec into running pods. Deploy runtime threat detection with Falco. Falco monitors system calls at the kernel level and alerts on anomalous behavior — unexpected shell executions inside containers, sensitive file reads, outbound connections to unknown IPs. Treat Falco alerts as high-priority security events. Monitor security metrics with Prometheus. Track RBAC denial counts, failed authentication attempts, image pull errors, and NetworkPolicy drop counts. Build Grafana dashboards for real-time cluster security posture visibility. 7. Runtime Security Even with strong admission controls and image scanning, runtime protection is essential. Containers share the host kernel, and a kernel exploit from within a container can compromise the entire node. Apply seccomp profiles to restrict system calls. Use the RuntimeDefault seccomp profile at minimum. For high-value workloads, create custom profiles using tools like seccomp-profile-recorder that whitelist only the syscalls your application uses. Enforce AppArmor or SELinux profiles. Mandatory Access Control systems add restriction layers beyond Linux discretionary access controls. Assign profiles to pods that limit file access, network operations, and capability usage at the OS level. Use read-only root filesystems. Set readOnlyRootFilesystem: true in the pod security context. This prevents attackers from writing malicious binaries or scripts. Mount emptyDir volumes for directories your application mu


---
## GitOps Security Patterns for Kubernetes

- URL: https://orthogonal.info/gitops-security-patterns-for-kubernetes/
- Date: 2026-03-13
- Category: DevOps
- Summary: Production-proven GitOps security patterns for Kubernetes. Covers sealed secrets, policy-as-code, drift detection, and secure ArgoCD configurations.

I’ve set up GitOps pipelines for Kubernetes clusters ranging from my homelab to enterprise fleets. The security mistakes are always the same: secrets in git, no commit signing, and wide-open deploy permissions. After hardening dozens of these pipelines, here are the patterns that actually survive contact with production. Introduction to GitOps and Security Challenges 📌 TL;DR: Explore production-proven GitOps security patterns for Kubernetes with a security-first approach to DevSecOps, ensuring solid and scalable deployments. 🎯 Quick Answer: Production GitOps security requires three non-negotiable patterns: never store secrets in Git (use External Secrets Operator), enforce GPG commit signing on all deployment repos, and restrict CI/CD deploy permissions with least-privilege RBAC and separate service accounts per environment. It started with a simple question: “Why is our staging environment deploying changes that no one approved?” That one question led me down a rabbit hole of misconfigured GitOps workflows, unchecked permissions, and a lack of traceability. If you’ve ever felt the sting of a rogue deployment or wondered how secure your GitOps pipeline really is, you’re not alone. GitOps, at its core, is a methodology that uses Git as the single source of truth for defining and managing application and infrastructure deployments. It’s a big improvement for Kubernetes workflows, enabling declarative configuration and automated reconciliation. But as with any powerful tool, GitOps comes with its own set of security challenges. Misconfigured permissions, unverified commits, and insecure secrets management can quickly turn your pipeline into a ticking time bomb. In a DevSecOps world, security isn’t optional—it’s foundational. A security-first mindset ensures that your GitOps workflows are not just functional but resilient against threats. Let’s dive into the core principles and battle-tested patterns that can help you secure your GitOps pipeline for Kubernetes. Another common challenge is the lack of visibility into changes happening within the pipeline. Without proper monitoring and alerting mechanisms, unauthorized or accidental changes can go unnoticed until they cause disruptions. This is especially critical in production environments where downtime can lead to significant financial and reputational losses. GitOps also introduces unique attack vectors, such as the risk of supply chain attacks. Malicious actors may attempt to inject vulnerabilities into your repository or compromise your CI/CD tooling. Addressing these risks requires a complete approach to security that spans both infrastructure and application layers. 💡 Pro Tip: Regularly audit your Git repository for unusual activity, such as unexpected branch creations or commits from unknown users. Tools like GitGuardian can help automate this process. If you’re new to GitOps, start by securing your staging environment first. This allows you to test security measures without impacting production workloads. Once you’ve validated your approach, gradually roll out changes to other environments. Core Security Principles for GitOps Before we get into the nitty-gritty of implementation, let’s talk about the foundational security principles that every GitOps workflow should follow. These principles are the bedrock of a secure and scalable pipeline. Principle of Least Privilege One of the most overlooked aspects of GitOps security is access control. The principle of least privilege dictates that every user, service, and process should have only the permissions necessary to perform their tasks—nothing more. In GitOps, this means tightly controlling who can push changes to your Git repository and who can trigger deployments. For example, if your GitOps operator only needs to deploy applications to a specific namespace, ensure that its Kubernetes Role-Based Access Control (RBAC) configuration limits access to that namespace. For a full guide, see our Kubernetes Security Checklist. Avoid granting cluster-wide permissions unless absolutely necessary. # Example: RBAC configuration for GitOps operator apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: my-namespace name: gitops-operator-role rules: - apiGroups: [""] resources: ["pods", "services"] verbs: ["get", "list", "watch"] Also, consider implementing multi-factor authentication (MFA) for users who have access to your Git repository. This adds an extra layer of security and reduces the risk of unauthorized access. 💡 Pro Tip: Regularly review and prune unused permissions in your RBAC configurations to minimize your attack surface. Secure Secrets Management ⚠️ Tradeoff: Sealed Secrets and SOPS both solve the “secrets in git” problem, but differently. Sealed Secrets are simpler but cluster-specific — migrating to a new cluster means re-encrypting everything. SOPS is more flexible but requires key management infrastructure. I use SOPS with age keys for my homelab and Vault-backed encryption for production. Secrets are the lifeblood of any deployment pipeline—API keys, database passwords, and encryption keys all flow through your GitOps workflows. Storing these secrets securely is non-negotiable. Tools like HashiCorp Vault, Kubernetes Secrets, and external secret management solutions can help keep sensitive data safe. For instance, you can use Kubernetes Secrets to store sensitive information and configure your GitOps operator to pull these secrets during deployment. However, Kubernetes Secrets are stored in plain text by default, so it’s advisable to encrypt them using tools like Sealed Secrets or external encryption mechanisms. # Example: Creating a Kubernetes Secret apiVersion: v1 kind: Secret metadata: name: my-secret type: Opaque data: password: bXktc2VjcmV0LXBhc3N3b3Jk ⚠️ Security Note: Avoid committing secrets directly to your Git repository, even if they are encrypted. Use external secret management tools whenever possible. Auditability and Traceability GitOps thrives on automation, but automation without accountability is a recipe for disaster. Every change in your pipeline should be traceable back to its origin. This means enabling detailed logging, tracking commit history, and ensuring that every deployment is tied to a verified change. Auditability isn’t just about compliance—it’s about knowing who did what, when, and why. This is invaluable during incident response and post-mortem analysis. For example, you can use Git hooks to enforce commit message standards that include ticket numbers or change descriptions. # Example: Git hook to enforce commit message format #!/bin/sh commit_message=$(cat $1) if ! echo "$commit_message" | grep -qE "^(JIRA-[0-9]+|FEATURE-[0-9]+):"; then echo "Error: Commit message must include a ticket number." exit 1 fi 💡 Pro Tip: Use tools like Elasticsearch or Loki to aggregate logs from your GitOps operator and Kubernetes cluster for centralized monitoring. Battle-Tested Security Patterns for GitOps Now that we’ve covered the principles, let’s dive into actionable security patterns that have been proven in production environments. These patterns will help you build a resilient GitOps pipeline that can withstand real-world threats. Signed Commits and Verified Deployments 🔍 Lesson learned: A junior engineer once pushed a config change that disabled network policies cluster-wide — it passed code review because the YAML diff looked harmless. After that, I added OPA Gatekeeper policies that block any change to critical security resources without a second approval. Automated policy gates catch what human reviewers miss. One of the simplest yet most effective security measures is signing your Git commits. Signed commits ensure that every change in your repository is authenticated and can be traced back to its author. Combine this with verified deployments to ensure that only trusted changes make it to your cluster. # Example: Signing a Git commit git commit -S -m "Secure commit message" # Verify the signature


---
## Engineer’s Guide to RSI, Ichimoku, Stochastic Indicators

- URL: https://orthogonal.info/engineers-guide-to-rsi-ichimoku-stochastic-indicators/
- Date: 2026-03-13
- Category: Finance &amp; Trading
- Summary: Deep dive into RSI, Ichimoku, and Stochastic indicators with Python code. Covers the math, signal generation, and backtesting for quantitative traders.

Dive into the math and code behind RSI, Ichimoku, and Stochastic indicators, exploring their quantitative foundations and Python implementations for finance engineers. Introduction to Technical Indicators 📌 TL;DR: Dive into the math and code behind RSI, Ichimoku, and Stochastic indicators, exploring their quantitative foundations and Python implementations for finance engineers. 🎯 Quick Answer: RSI measures momentum on a 0–100 scale (below 30 = oversold, above 70 = overbought), Ichimoku provides trend direction via cloud positioning, and Stochastic compares closing price to its range. Combine all three for higher-confidence signals than any single indicator alone. I built a multi-agent trading system in Python and LangGraph that analyzes SEC filings, options flow, and technical indicators across 50+ tickers simultaneously. When I started, I made the mistake most engineers make—I treated indicators as black boxes. That cost me real money. Here’s the technical framework I wish I’d had from day one. Technical indicators are mathematical calculations applied to price, volume, or other market data to forecast trends and make trading decisions. For engineers, indicators should be approached with a math-heavy, code-first mindset. Understanding their formulas, statistical foundations, and implementation nuances is crucial to building robust trading systems. We’ll dive deep into three popular indicators: Relative Strength Index (RSI), Ichimoku Cloud, and Stochastic Oscillator. We’ll break down their mathematical foundations, implement them in Python, and explore their practical applications. 💡 Pro Tip: Always test your indicators on multiple datasets and market conditions during backtesting. This helps identify scenarios where they fail and ensures robustness in live trading. Mathematical Foundations of RSI, Ichimoku, and Stochastic 📊 Real example: My trading system caught a divergence between RSI and price action on AAPL last quarter—RSI was making lower highs while price made higher highs. The signal was correct: price reversed 8% over the next 3 weeks. Without coding my own RSI implementation, I would have missed the divergence window entirely. Relative Strength Index (RSI) The RSI is a momentum oscillator that measures the speed and change of price movements. It oscillates between 0 and 100, with values above 70 typically indicating overbought conditions and values below 30 signaling oversold conditions. The formula for RSI is: RSI = 100 - (100 / (1 + RS)) Where RS (Relative Strength) is calculated as: RS = Average Gain / Average Loss RSI is particularly useful for identifying potential reversal points in trending markets. For example, if a stock’s RSI crosses above 70, it might indicate that the asset is overbought and due for a correction. Conversely, an RSI below 30 could signal oversold conditions, suggesting a potential rebound. However, RSI is not foolproof. In strongly trending markets, RSI can remain in overbought or oversold territory for extended periods, leading to false signals. Engineers should consider pairing RSI with trend-following indicators like moving averages to filter out noise. 💡 Pro Tip: Use RSI divergence as a powerful signal. If the price makes a new high while RSI fails to do so, it could indicate weakening momentum and a potential reversal. To illustrate, let’s consider a stock that has been rallying for several weeks. If the RSI crosses above 70 but the stock’s price action shows signs of slowing down, such as smaller daily gains or increased volatility, it might be time to consider exiting the position or tightening stop-loss levels. Here’s an additional Python snippet for calculating RSI with error handling for missing data: import pandas as pd import numpy as np def calculate_rsi(data, period=14): if 'Close' not in data.columns: raise ValueError("Data must contain a 'Close' column.") delta = data['Close'].diff() gain = np.where(delta > 0, delta, 0) loss = np.where(delta < 0, abs(delta), 0) avg_gain = pd.Series(gain).rolling(window=period, min_periods=1).mean() avg_loss = pd.Series(loss).rolling(window=period, min_periods=1).mean() rs = avg_gain / avg_loss rsi = 100 - (100 / (1 + rs)) return rsi # Example usage data = pd.read_csv('market_data.csv') data['RSI'] = calculate_rsi(data) ⚠️ Security Note: Always validate your input data for missing values before performing calculations. Missing data can skew your RSI results. Ichimoku Cloud 🔧 Why I built this into my pipeline: Manual chart analysis doesn’t scale. When you’re monitoring 50+ tickers across multiple timeframes, you need code that computes these indicators in real-time and alerts you to divergences. My system runs these calculations every 5 minutes during market hours. The Ichimoku Cloud, or Ichimoku Kinko Hyo, is a complete indicator that provides insights into trend direction, support/resistance levels, and momentum. It consists of five main components: Tenkan-sen (Conversion Line): (9-period high + 9-period low) / 2 Kijun-sen (Base Line): (26-period high + 26-period low) / 2 Senkou Span A (Leading Span A): (Tenkan-sen + Kijun-sen) / 2 Senkou Span B (Leading Span B): (52-period high + 52-period low) / 2 Chikou Span (Lagging Span): Current closing price plotted 26 periods back Ichimoku Cloud is particularly effective in trending markets. For example, when the price is above the cloud, it signals an uptrend, while a price below the cloud indicates a downtrend. The cloud itself acts as a dynamic support/resistance zone. One common mistake traders make is using Ichimoku Cloud with its default parameters (9, 26, 52) without considering the market they’re trading in. These settings were optimized for Japanese markets, which have different trading dynamics compared to U.S. or European markets. 💡 Pro Tip: Adjust Ichimoku parameters based on the asset’s volatility and trading hours. For example, use shorter periods for highly volatile assets like cryptocurrencies. Here’s an enhanced Python implementation for Ichimoku Cloud: def calculate_ichimoku(data): if not {'High', 'Low', 'Close'}.issubset(data.columns): raise ValueError("Data must contain 'High', 'Low', and 'Close' columns.") data['Tenkan_sen'] = (data['High'].rolling(window=9).max() + data['Low'].rolling(window=9).min()) / 2 data['Kijun_sen'] = (data['High'].rolling(window=26).max() + data['Low'].rolling(window=26).min()) / 2 data['Senkou_span_a'] = ((data['Tenkan_sen'] + data['Kijun_sen']) / 2).shift(26) data['Senkou_span_b'] = ((data['High'].rolling(window=52).max() + data['Low'].rolling(window=52).min()) / 2).shift(26) data['Chikou_span'] = data['Close'].shift(-26) return data # Example usage data = pd.read_csv('market_data.csv') data = calculate_ichimoku(data) ⚠️ Security Note: Ensure your data is clean and free of outliers before calculating Ichimoku components. Outliers can distort the cloud and lead to false signals. Stochastic Oscillator The stochastic oscillator compares a security’s closing price to its price range over a specified period. It consists of two lines: %K and %D. The formula for %K is: %K = ((Current Close - Lowest Low) / (Highest High - Lowest Low)) * 100 %D is a 3-period moving average of %K. Stochastic indicators are particularly useful in range-bound markets. For example, when %K crosses above %D in oversold territory (below 20), it signals a potential buying opportunity. Conversely, a crossover in overbought territory (above 80) suggests a potential sell signal. 💡 Pro Tip: Combine stochastic signals with candlestick patterns like engulfing or pin bars for more reliable entry/exit points. Here’s an enhanced Python implementation for the stochastic oscillator: def calculate_stochastic(data, period=14): if not {'High', 'Low', 'Close'}.issubset(data.columns): raise ValueError("Data must contain 'High', 'Low', and 'Close' columns.") data['Lowest_low'] = data['Low'].rolling(window=period).min() data['Highest_high'] = data['High'].rolling(window=period).max() data['%K'] = ((data['Close'] - data['


---
## Secure Coding Patterns for Every Developer

- URL: https://orthogonal.info/secure-coding-patterns-for-every-developer/
- Date: 2026-03-13
- Category: Security
- Summary: Practical secure coding patterns every developer should know. Covers input validation, auth flows, secrets handling, and defense-in-depth strategies.

After 12 years reviewing code in Big Tech security teams, I can tell you the same vulnerabilities show up in every codebase: unsanitized inputs, broken auth, and secrets in source code. These aren’t exotic attacks — they’re patterns that any developer can learn to prevent. Here are the secure coding patterns I teach every engineer I mentor. Why Security is a Developer’s Responsibility 📌 TL;DR: Learn practical secure coding patterns that help developers to build resilient applications without relying solely on security teams. Why Security is a Developer’s Responsibility The error was catastrophic: a simple SQL injection attack had exposed thousands of user records. 🎯 Quick Answer: After 12 years in Big Tech security, the same three vulnerabilities appear everywhere: unsanitized user inputs causing injection attacks, broken authentication logic, and secrets hardcoded in source code. Fixing just these three patterns eliminates the majority of exploitable bugs in production applications. The error was catastrophic: a simple SQL injection attack had exposed thousands of user records. The developers were blindsided. “But we have a security team,” one of them protested. Sound familiar? If you’ve ever thought security was someone else’s job, you’re not alone—but you’re also wrong. In today’s fast-paced development environments, the lines between roles are blurring. Developers are no longer just writing code; they’re deploying it, monitoring it, and yes, securing it. The rise of DevOps and cloud-native architectures means that insecure code can lead to vulnerabilities that ripple across entire systems. From misconfigured APIs to hardcoded secrets, developers are often the first—and sometimes the last—line of defense against attackers. Consider some of the most infamous breaches in recent years. Many of them stemmed from insecure code: unvalidated inputs, poorly managed secrets, or weak authentication mechanisms. These aren’t just technical mistakes—they’re missed opportunities to bake security into the development process. And here’s the kicker: security teams can’t fix what they don’t know about. Developers must take ownership of secure coding practices to bridge the gap between development and security teams. Another reason security is a developer’s responsibility is the sheer speed of modern development cycles. Continuous Integration and Continuous Deployment (CI/CD) pipelines mean that code often goes live within hours of being written. If security isn’t baked into the code from the start, vulnerabilities can be deployed just as quickly as features. This makes it critical for developers to adopt a security-first mindset, ensuring that every line of code they write is resilient against potential threats. Real-world examples highlight the consequences of neglecting security. In 2017, the Equifax breach exposed the personal data of 147 million people. The root cause? A failure to patch a known vulnerability in an open-source library. While patching isn’t always a developer’s direct responsibility, understanding the security implications of third-party dependencies is. Developers must stay vigilant, regularly auditing and updating the libraries and frameworks they use. 💡 Pro Tip: Treat security as a feature, not an afterthought. Just as you would prioritize performance or scalability, make security a non-negotiable part of your development process. Troubleshooting Guidance: If you’re unsure where to start, begin by identifying the most critical parts of your application. Focus on securing areas that handle sensitive data, such as user authentication or payment processing. Use tools like dependency checkers to identify vulnerabilities in third-party libraries. Core Principles of Secure Coding Before diving into specific patterns, let’s talk about the foundational principles that guide secure coding. These aren’t just buzzwords—they’re the bedrock of resilient applications. Understanding the Principle of Least Privilege Imagine you’re hosting a party. You wouldn’t hand out keys to your bedroom or safe to every guest, right? The same logic applies to software. The principle of least privilege dictates that every component—whether it’s a user, process, or service—should only have the permissions it absolutely needs to perform its function. Nothing more. For example, a database connection used by your application shouldn’t have admin privileges unless it’s explicitly required. Over-permissioning is a common mistake that attackers exploit to escalate their access. In practice, implementing least privilege can involve setting up role-based access control (RBAC) systems. For instance, in a web application, an admin user might have permissions to delete records, while a regular user can only view them. By clearly defining roles and permissions, you minimize the risk of accidental or malicious misuse. { "roles": { "admin": ["read", "write", "delete"], "user": ["read"] } } ⚠️ Security Note: Audit permissions regularly. Over time, roles and privileges tend to accumulate unnecessary access. Troubleshooting Guidance: If you encounter permission-related errors, use logging to identify which roles or users are attempting unauthorized actions. This can help you fine-tune your access control policies. The Importance of Input Validation and Sanitization 🔍 Lesson learned: I once found a SQL injection vulnerability in an internal tool that had been in production for three years. It was a simple admin dashboard that “only internal users” accessed — but it had no input sanitization at all. Internal doesn’t mean safe. Every input boundary is a trust boundary, period. If you’ve ever seen an error like “unexpected token” or “syntax error,” you know how dangerous unvalidated inputs can be. Attackers thrive on poorly validated inputs, using them to inject malicious code, crash systems, or exfiltrate data. Input validation ensures that user-provided data conforms to expected formats, while sanitization removes or escapes potentially harmful characters. For example, when accepting user input for a search query, validate that the input contains only alphanumeric characters. If you’re working with database queries, use parameterized queries to prevent SQL injection. Consider a real-world scenario: a login form that accepts a username and password. Without proper validation, an attacker could inject SQL commands into the username field to bypass authentication. By validating the input and using parameterized queries, you can neutralize this threat. const username = req.body.username; if (!/^[a-zA-Z0-9]+$/.test(username)) { throw new Error("Invalid username format"); } 💡 Pro Tip: Always validate inputs on both the client and server sides. Client-side validation improves user experience, while server-side validation ensures security. Troubleshooting Guidance: If input validation is causing issues, check your validation rules and error messages. Ensure that they are clear and provide actionable feedback to users. Using Secure Defaults to Minimize Risk Convenience is the enemy of security. Default configurations often prioritize ease of use over safety, leaving applications exposed. Secure defaults mean starting with the most restrictive settings and allowing developers to loosen them only when absolutely necessary. For instance, a new database should have encryption enabled by default, and a web application should reject insecure HTTP traffic unless explicitly configured otherwise. Another example is file uploads. By default, your application should reject executable file types like .exe or .sh. If you need to allow specific file types, explicitly whitelist them rather than relying on a blacklist. ALLOWED_FILE_TYPES = ["image/jpeg", "image/png"] def is_allowed_file(file_type): return file_type in ALLOWED_FILE_TYPES 💡 Pro Tip: Regularly review your application’s default settings to ensure they align with current security best practices. Troubleshooting Guidance: If secure defaults are causing 


---
## Secure C# ConcurrentDictionary for Production

- URL: https://orthogonal.info/secure-c-concurrentdictionary-for-production/
- Date: 2026-03-13
- Category: DevOps
- Summary: Production-ready C# ConcurrentDictionary with a security-first approach. Covers thread safety, DoS-resistant patterns, and DevSecOps best practices.

I’ve debugged more ConcurrentDictionary race conditions than I care to admit. Thread-safe doesn’t mean bug-free — it means the failure modes are subtler and harder to reproduce. After shipping high-throughput C# services in production environments, here’s what I’ve learned about making ConcurrentDictionary actually production-ready. See also our guide on ConcurrentDictionary in Kubernetes environments. See also our guide on Docker memory management. Introduction to ConcurrentDictionary in C# 📌 TL;DR: Explore a security-first, production-ready approach to using C# ConcurrentDictionary, combining performance and DevSecOps best practices. See also our guide on ConcurrentDictionary in Kubernetes environments . See also our guide on Docker memory management . 🎯 Quick Answer: C# ConcurrentDictionary is thread-safe for individual operations but not for compound read-then-write sequences. Always use GetOrAdd() or AddOrUpdate() with factory delegates instead of checking ContainsKey() then adding, and validate all inputs before insertion to prevent injection via dictionary keys. Most developers think using a thread-safe collection like ConcurrentDictionary automatically solves all concurrency issues. It doesn’t. In the world of .NET programming, ConcurrentDictionary is often hailed as a silver bullet for handling concurrent access to shared data. It’s a part of the System.Collections.Concurrent namespace and is designed to provide thread-safe operations without requiring additional locks. At first glance, it seems like the perfect solution for multi-threaded applications. But as with any tool, improper usage can lead to subtle bugs, performance bottlenecks, and even security vulnerabilities. Thread-safe collections like ConcurrentDictionary are critical in modern applications, especially when dealing with multi-threaded or asynchronous code. They allow multiple threads to read and write to a shared collection without causing data corruption. However, just because something is thread-safe doesn’t mean it’s foolproof. Understanding how ConcurrentDictionary works under the hood is essential to using it effectively and securely in production environments. For example, imagine a scenario where multiple threads are trying to update a shared cache of product prices in an e-commerce application. While ConcurrentDictionary ensures that no two threads corrupt the internal state of the dictionary, it doesn’t prevent logical errors such as overwriting a price with stale data. This highlights the importance of understanding the nuances of thread-safe collections. Also, ConcurrentDictionary offers several methods like TryAdd, TryUpdate, and GetOrAdd that simplify common concurrency patterns. However, developers must be cautious about how these methods are used, especially in scenarios involving complex business logic. 💡 Pro Tip: Use GetOrAdd when you need to initialize a value only if it doesn’t already exist. This method is both thread-safe and efficient for such use cases. we’ll explore the common pitfalls developers face when using ConcurrentDictionary, the security implications of improper usage, and how to implement it in a way that balances performance and security. Whether you’re new to concurrent programming or a seasoned developer, there’s something here for you. var dictionary = new ConcurrentDictionary<string, int>(); // Example: Using GetOrAdd int value = dictionary.GetOrAdd("key1", key => ComputeValue(key)); Console.WriteLine($"Value for key1: {value}"); // ComputeValue is a method that calculates the value if the key doesn't exist int ComputeValue(string key) { return key.Length * 10; } Concurrency and Security: Challenges in Production 🔍 Lesson learned: We had a rate limiter built on ConcurrentDictionary that worked perfectly in testing. In production under high load, the GetOrAdd factory delegate was being called multiple times for the same key — creating duplicate rate limit windows. The fix was using Lazy<T> as the value type to ensure single initialization. This subtle behavior isn’t in most tutorials. Concurrency is a double-edged sword. On one hand, it allows applications to perform multiple tasks simultaneously, improving performance and responsiveness. On the other hand, it introduces complexities like race conditions, deadlocks, and data corruption. When it comes to ConcurrentDictionary, these issues can manifest in subtle and unexpected ways, especially when developers make incorrect assumptions about its behavior. One common misconception is that ConcurrentDictionary eliminates the need for all synchronization. While it does handle basic thread-safety for operations like adding, updating, or retrieving items, it doesn’t guarantee atomicity across multiple operations. For example, checking if a key exists and then adding it is not atomic. This can lead to race conditions where multiple threads try to add the same key simultaneously, causing unexpected behavior. Consider a real-world example: a web application that uses ConcurrentDictionary to store user session data. If multiple threads attempt to create a session for the same user simultaneously, the application might end up with duplicate or inconsistent session entries. This can lead to issues like users being logged out unexpectedly or seeing incorrect session data. From a security perspective, improper usage of ConcurrentDictionary can open the door to vulnerabilities. Consider a scenario where the dictionary is used to cache user authentication tokens. If an attacker can exploit a race condition to overwrite a token or inject malicious data, the entire authentication mechanism could be compromised. These are not just theoretical risks; real-world incidents have shown how concurrency issues can lead to severe security breaches. ⚠️ Security Note: Always assume that concurrent operations can be exploited if not properly secured. A race condition in your code could be a vulnerability in someone else’s exploit toolkit. To mitigate these risks, developers should carefully analyze the concurrency requirements of their applications and use additional synchronization mechanisms when necessary. For example, wrapping critical sections of code in a lock statement can ensure that only one thread executes the code at a time. private readonly object _syncLock = new object(); private readonly ConcurrentDictionary<string, string> _sessionCache = new ConcurrentDictionary<string, string>(); public void AddOrUpdateSession(string userId, string sessionData) { lock (_syncLock) { _sessionCache[userId] = sessionData; } } Best Practices for Secure Implementation Using ConcurrentDictionary securely in production requires more than just calling its methods. You need to adopt a security-first mindset and follow best practices to ensure both thread-safety and data integrity. 1. Use Proper Locking Mechanisms While ConcurrentDictionary is thread-safe for individual operations, there are cases where you need to perform multiple operations atomically. In such scenarios, using a lock or other synchronization mechanism is essential. For example, if you need to check if a key exists and then add it, you should wrap these operations in a lock to prevent race conditions. private readonly object _lock = new object(); private readonly ConcurrentDictionary<string, int> _dictionary = new ConcurrentDictionary<string, int>(); public void AddIfNotExists(string key, int value) { lock (_lock) { if (!_dictionary.ContainsKey(key)) { _dictionary[key] = value; } } } 2. Validate and Sanitize Inputs ⚠️ Tradeoff: Adding input validation to every dictionary operation adds measurable latency at high throughput. In one service handling 50K requests/second, validation added 2ms p99 latency. My approach: validate at the boundary (API layer) and trust internal callers, rather than validating at every dictionary access. Defense in depth doesn’t mean redundant checks on every line. Never trust user input, even when using a thread-safe collection. Alway


---
## Self-Hosted GitOps Pipeline: Gitea + ArgoCD Guide

- URL: https://orthogonal.info/build-a-self-hosted-gitops-pipeline-with-gitea-argocd-and-kubernetes-at-home/
- Date: 2026-03-02
- Category: Deep Dives
- Summary: Build a self-hosted GitOps pipeline with Gitea and ArgoCD. Step-by-step guide covering Git setup, CI/CD integration, secrets, and automated deployments.

I self-host Gitea on my TrueNAS homelab and use it to deploy everything from trading bots to media servers. The error message that started this guide was maddening: “Permission denied while cloning repository.” It was my repository. On my server. In my basement. Yet somehow, my GitOps pipeline decided to stage a mutiny. If you’ve ever felt personally attacked by your own self-hosted CI/CD setup, you’re not alone. This article is here to save your sanity (and maybe your cat’s life). We’re diving deep into building a self-hosted GitOps pipeline using Gitea, ArgoCD, and Kubernetes on your home lab. Whether you’re a homelab enthusiast or a DevOps engineer tired of fighting with cloud services, this guide will help you take back control. No more cryptic errors, no more dependency nightmares—just a clean, reliable pipeline that works exactly how you want it to. Let’s roll up our sleeves and fix this mess. What is GitOps and Why Self-Host? 🔧 From my experience: The biggest win of self-hosted GitOps isn’t automation—it’s auditability. Every change to my infrastructure is a git commit with a timestamp and a diff. When something breaks at 2 AM, I run git log --oneline -5 and immediately see what changed. That alone has saved me hours of debugging. 📌 TL;DR: The error message was maddening: “Permission denied while cloning repository.” It was my repository. I own everything here, including the questionable Wi-Fi router and the cat that keeps unplugging cables. Yet somehow, my GitOps pipeline decided to stage a mutiny. 🎯 Quick Answer: A self-hosted GitOps pipeline using Gitea as the Git server and ArgoCD for continuous deployment provides full CI/CD control on homelab or TrueNAS hardware without relying on GitHub or cloud services. Gitea handles repositories and webhooks while ArgoCD syncs cluster state from Git. GitOps is a big improvement for managing infrastructure and application deployments. At its core, GitOps means using Git as the single source of truth for your system’s desired state. Instead of manually tweaking configurations or relying on someone’s “I swear this works” bash script, GitOps lets you define everything declaratively in Git repositories. Kubernetes then syncs your cluster to match the state defined in Git. It’s automated, repeatable, and—when done right—beautifully simple. But why self-host your CI/CD pipeline? For homelab enthusiasts, self-hosting is the ultimate flex. It’s like growing your own vegetables instead of buying them at the store. You get full control, no vendor lock-in, and the satisfaction of knowing you’re running everything on your own hardware. For DevOps engineers, self-hosting means tailoring the pipeline to your exact needs, ensuring workflows are as efficient—or chaotic—as you want them to be. 💡 Pro Tip: Start small with a single project before going full GitOps on your entire homelab. Debugging a broken pipeline at 2 AM is not fun. Key Tools for Your Pipeline Gitea: A lightweight, self-hosted Git service. Think of it as GitHub’s chill cousin who doesn’t charge you for private repos. ArgoCD: The GitOps powerhouse that syncs your Git repositories with your Kubernetes clusters. It’s like having a personal assistant for your deployments. Kubernetes: The container orchestration king. If you’re not using Kubernetes yet, prepare for a rabbit hole of YAML files and endless possibilities. 🔐 Security Note: Self-hosting means you’re responsible for securing your pipeline. Always use HTTPS, configure firewalls, and limit access to your repositories. Step 1: Setting Up Your Home Kubernetes Cluster Setting up a Kubernetes cluster at home is both thrilling and maddening. Think of it like assembling IKEA furniture, but instead of a bookshelf, you’re building a self-hosted CI/CD powerhouse. Let’s break it down. Hardware Requirements You don’t need a data center in your basement (though if you have one, I’m jealous). A few low-power devices like Raspberry Pis or Intel NUCs will do the trick. Here’s what you’ll need: Raspberry Pi: Affordable and power-efficient. Go for the 4GB or 8GB models. Intel NUC: More powerful than a Pi, great for running heavier workloads like Gitea or ArgoCD. Storage: Use SSDs for speed. Slow storage will bottleneck your CI/CD jobs. Networking: A decent router or switch is essential. VLAN support is a bonus for network segmentation. 💡 Pro Tip: If you’re using Raspberry Pis, invest in a reliable USB-C power supply. Flaky power leads to flaky clusters. Installing Kubernetes with k3s For simplicity, we’ll use k3s, a lightweight Kubernetes distribution perfect for home labs. Here’s how to get started: # Download the k3s installation script curl -sfL https://get.k3s.io -o install-k3s.sh # Verify the script's integrity (check the official k3s site for checksum details) sha256sum install-k3s.sh # Run the script manually after verification sudo sh install-k3s.sh # Check if k3s is running sudo kubectl get nodes # Join worker nodes to the cluster curl -sfL https://get.k3s.io -o install-k3s-worker.sh sha256sum install-k3s-worker.sh sudo sh install-k3s-worker.sh K3S_URL=https://<MASTER_IP>:6443 K3S_TOKEN=<TOKEN> Replace <MASTER_IP> and <TOKEN> with the actual values from your master node. The token can be found in /var/lib/rancher/k3s/server/node-token on the master. 🔐 Security Note: Avoid exposing your Kubernetes API to the internet. Use a VPN or SSH tunnel for remote access. Optimizing Kubernetes for Minimal Infrastructure Running Kubernetes on a shoestring budget? Here are some tips: Use GitOps: Tools like ArgoCD automate deployments and keep your cluster configuration in sync with Git. Self-host Gitea: Gitea is lightweight and perfect for managing your CI/CD pipelines without hogging resources. Resource Limits: Set CPU and memory limits for your pods to prevent one rogue app from taking down your cluster. Node Affinity: Use node affinity rules to run critical workloads on your most reliable hardware. 💡 Pro Tip: If you’re running out of resources, consider offloading non-critical workloads to a cloud provider. Hybrid clusters are a thing! Step 2: Deploying Gitea for Self-Hosted Git Repositories Gitea is a lightweight, self-hosted Git service that’s perfect for homelabs and serious DevOps workflows. Here’s how to deploy it: Deploying Gitea with Helm # Add the Gitea Helm repo helm repo add gitea-charts https://dl.gitea.io/charts/ # Install Gitea with default values helm install my-gitea gitea-charts/gitea Once deployed, configure Gitea for secure repository management: Enable HTTPS: Use a reverse proxy like Nginx or Traefik for SSL termination. Set User Permissions: Carefully configure access to prevent accidental force-pushes to main. Use Webhooks: Integrate Gitea with ArgoCD or other automation tools for seamless CI/CD workflows. 💡 Pro Tip: Use Gitea’s built-in API for automation. It’s like having a personal assistant for your repositories. Step 3: Integrating ArgoCD for GitOps ArgoCD is the glue that binds your Git repositories to your Kubernetes cluster. Here’s how to set it up: # Add the ArgoCD Helm repo helm repo add argo https://argoproj.github.io/argo-helm # Install ArgoCD helm install my-argocd argo/argo-cd Once installed, configure ArgoCD to sync your repositories with your cluster: Define Applications: Use ArgoCD manifests to specify which repositories and branches to sync. Automate Sync: Enable auto-sync to keep your cluster up-to-date with Git. Monitor Health: Use ArgoCD’s dashboard to monitor application health and sync status. ⚠️ Gotcha: ArgoCD’s default settings may not be secure for production. Always review and harden configurations. Conclusion Building a self-hosted GitOps pipeline with Gitea, ArgoCD, and Kubernetes is one of the most rewarding homelab projects I’ve done. Once it clicks, you’ll never want to deploy manually again. Here’s what we covered: GitOps simplifies infrastructure management by using Git as the single source of truth. Self-hosting gives you full control over your CI/CD workflows


---
## Why AI Makes Architecture the Only Skill That Matters

- URL: https://orthogonal.info/ai-plan-driven-development-architecture-only-skill-that-matters/
- Date: 2026-02-13
- Category: Deep Dives
- Summary: AI writes code faster than ever, but architecture skills matter more. Why system design is the moat that separates senior engineers from the rest.

Last month, I built a complete microservice in a single afternoon. Not a prototype. Not a proof-of-concept. A production-grade service with authentication, rate limiting, PostgreSQL integration, full test coverage, OpenAPI docs, and a CI/CD pipeline. Containerized, deployed, monitoring configured. The kind of thing that would have taken my team two to three sprints eighteen months ago. I didn’t write most of the code. I wrote the plan. And I think that moment—sitting there watching Claude Code churn through my architecture doc, implementing exactly what I’d specified while I reviewed each module—was the exact moment I realized the industry has already changed. We just haven’t processed it yet. The Numbers Don’t Lie (But They Do Confuse) 📌 TL;DR: Last month, I built a complete microservice in a single afternoon. Not a proof-of-concept. A production-grade service with authentication, rate limiting, PostgreSQL integration, full test coverage, OpenAPI docs, and a CI/CD pipeline. 🎯 Quick Answer: AI can generate a complete production microservice in one afternoon, making implementation speed nearly free. The irreplaceable skill is system architecture—deciding service boundaries, data flows, failure modes, and integration patterns—because AI executes well but cannot make high-level design decisions autonomously. Let me lay out the landscape, because it’s genuinely contradictory right now: Anthropic—the company behind Claude, valued at $380 billion as of this week—published a study showing that AI-assisted coding “doesn’t show significant efficiency gains” and may impair developers’ understanding of their own codebases. Meanwhile, Y Combinator reported that 25% of startups in its Winter 2025 batch had codebases that were 95% AI-generated. Indian IT stocks lost $50 billion in market cap in February 2026 alone on fears that AI is replacing outsourced development. GPT-5.3 Codex just launched. Gemini 3 Deep Think can reason through multi-file architectural changes. How do you reconcile “no efficiency gains” with “$50 billion in market value evaporating because AI is too efficient”? The answer is embarrassingly simple: the tool isn’t the bottleneck. The plan is. Key insight: AI doesn’t make bad plans faster. It makes good plans executable at near-zero marginal cost. The developers who aren’t seeing gains are the ones prompting without planning. The ones seeing 10x gains are the ones who spend 80% of their time on architecture, specs, and constraints—and 20% on execution. The Death of Implementation Cost I want to be precise about what’s happening, because the hype cycle makes everyone either a zealot or a denier. Here’s what I’m actually observing in my consulting work: The cost of translating a clear specification into working code is approaching zero. Not the cost of software. Not the cost of good software. The cost of the implementation step—the part where you take a well-defined plan and turn it into lines of code that compile and pass tests. This is a critical distinction. Building software involves roughly five layers: Understanding the problem — What are we actually solving? For whom? What are the constraints? Designing the solution — Architecture, data models, API contracts, security boundaries, failure modes Implementing the code — Translating the design into working software Validating correctness — Testing, security review, performance profiling Operating in production — Deployment, monitoring, incident response, iteration AI has made layer 3 nearly free. It has made modest improvements to layers 4 and 5. It has done almost nothing for layers 1 and 2. And that’s the punchline: layers 1 and 2 are where the actual value lives. They always were. We just used to pretend that “senior engineer” meant “person who writes code faster.” It never did. It meant “person who knows what to build and how to structure it.” Welcome to the Plan-Driven World Here’s what my workflow looks like now, and I’m seeing similar patterns emerge across every competent team I work with: Phase 1: The Specification (60-70% of total time) Before I write a single prompt, I write a plan. Not a Jira ticket with three bullet points. A real specification: ## Service: Rate Limiter ### Purpose Protect downstream APIs from abuse while allowing legitimate burst traffic. ### Architecture Decisions - Token bucket algorithm (not sliding window — we need burst tolerance) - Redis-backed (shared state across pods) - Per-user AND per-endpoint limits - Graceful degradation: if Redis is down, allow traffic (fail-open) with local in-memory fallback ### Security Requirements - No rate limit info in error responses (prevents enumeration) - Admin override via signed JWT (not API key) - Audit log for all limit changes ### API Contract POST /api/v1/check-limit Request: { "user_id": string, "endpoint": string, "weight": int } Response: { "allowed": bool, "remaining": int, "reset_at": ISO8601 } ### Failure Modes 1. Redis connection lost → fall back to local cache, alert ops 2. Clock skew between pods → use Redis TIME, not local clock 3. Memory pressure → evict oldest buckets first (LRU) ### Non-Requirements - We do NOT need distributed rate limiting across regions (yet) - We do NOT need real-time dashboard (batch analytics is fine) - We do NOT need webhook notifications on limit breach That spec took me 45 minutes. Notice what it includes: architecture decisions with reasoning, security requirements, failure modes, and explicitly stated non-requirements. The non-requirements are just as important—they prevent the AI from over-engineering things you don’t need. Phase 2: AI Implementation (10-15% of total time) I feed the spec to Claude Code. Within minutes, I have a working implementation. Not perfect—but structurally correct. The architecture matches. The API contract matches. The failure modes are handled. Phase 3: Review, Harden, Ship (20-25% of total time) This is where my 12 years of experience actually matter. I review every security boundary. I stress-test the failure modes. I look for the things AI consistently gets wrong—auth edge cases, CORS configurations, input validation. I add the monitoring that the AI forgot about because monitoring isn’t in most training data. Security note: The review phase is non-negotiable. I wrote extensively about why vibe coding is a security nightmare. The plan-driven approach works precisely because the plan includes security requirements that the AI must follow. Without the plan, AI defaults to insecure patterns. With the plan, you can verify compliance. What This Means for Companies The implications are enormous, and most organizations are still thinking about this wrong. Internal Development Cost Is Collapsing Consider the economics. A mid-level engineer costs a company $150-250K/year fully loaded. A team of five ships maybe 4-6 features per quarter. That’s roughly $40-60K per feature, if you’re generous with the accounting. Now consider: a senior architect with AI tools can ship the same feature set in a fraction of the time. Not because the AI is magic—but because the implementation step, which used to consume 60-70% of engineering time, is now nearly instant. The architect’s time goes into planning, reviewing, and operating. I’m watching this play out in real time. Companies that used to need 15-person engineering teams are running the same workload with 5. Not because 10 people got fired (though some did), but because a smaller team of more senior people can now execute faster with AI augmentation. The Reddit post from an EM with 10+ years of experience captures this perfectly: his team adopted Claude Code, built shared context and skills repositories, and now generates PRs “at the level of an upper mid-level engineer in one shot.” They built a new set of services “in half the time they normally experience.” The Outsourcing Apocalypse Is Real Indian IT stocks losing $50 billion in a single month isn’t irrational fear—it’s rational repricing. If a US-based archit


---
## Vibe Coding Is a Security Nightmare: How to Fix It

- URL: https://orthogonal.info/vibe-coding-security-nightmare-how-to-survive/
- Date: 2026-02-11
- Category: Deep Dives
- Summary: AI-generated code is a security minefield. Learn how to audit vibe-coded PRs, catch hidden vulnerabilities, and build secure code review workflows.

Three weeks ago I reviewed a pull request from a junior developer on our team. The code was clean—suspiciously clean. Good variable names, proper error handling, even JSDoc comments. I approved it, deployed it, and moved on. Then our SAST scanner flagged it. Hardcoded API keys in a utility function. An SQL query built with string concatenation buried inside a helper. A JWT validation that checked the signature but never verified the expiration. All wrapped in beautiful, well-commented code that looked like it was written by someone who knew what they were doing. “Oh yeah,” the junior said when I asked about it. “I vibed that whole module.” Welcome to 2026, where “vibe coding” isn’t just a meme—it’s Collins Dictionary’s Word of the Year for 2025, and it’s fundamentally reshaping how we think about software security. What Exactly Is Vibe Coding? 📌 TL;DR: Three weeks ago I reviewed a pull request from a junior developer on our team. The code was clean—suspiciously clean. Good variable names, proper error handling, even JSDoc comments. 🎯 Quick Answer: AI-generated code frequently introduces security vulnerabilities like hardcoded API keys that pass human code review undetected. Run SAST scanners (Semgrep, CodeQL) automatically on every AI-generated commit to catch secrets, injection flaws, and insecure patterns before they reach production. The term was coined by Andrej Karpathy, co-founder of OpenAI and former AI lead at Tesla, in February 2025. His definition was refreshingly honest: Karpathy’s original description: “You fully give in to the vibes, embrace exponentials, and forget that the code even exists. I ‘Accept All’ always, I don’t read the diffs anymore. When I get error messages I just copy paste them in with no comment.” That’s the key distinction. Using an LLM to help write code while reviewing every line? That’s AI-assisted development. Accepting whatever the model generates without understanding it? That’s vibe coding. As Simon Willison put it: “If an LLM wrote every line of your code, but you’ve reviewed, tested, and understood it all, that’s not vibe coding.” And look, I get the appeal. I’ve used Claude Code and Cursor extensively—I wrote about my Claude Code experience recently. These tools are genuinely powerful. But there’s a massive difference between using AI as a force multiplier and blindly accepting generated code into production. The Security Numbers Are Terrifying 🔍 From production: I also build algorithmic trading systems, where a single input validation bug could mean unauthorized trades or leaked API keys to a brokerage. I run every AI-generated code change through SAST and manual review—no exceptions, even for “obvious” utility functions. Let me throw some stats at you that should make any security engineer lose sleep: In December 2025, CodeRabbit analyzed 470 open-source GitHub pull requests and found that AI co-authored code contained 2.74x more security vulnerabilities than human-written code. Not 10% more. Not even double. Nearly triple. The same study found 1.7x more “major” issues overall, including logic errors, incorrect dependencies, flawed control flow, and misconfigurations that were 75% more common in AI-generated code. And then there’s the Lovable incident. In May 2025, security researchers discovered that 170 out of 1,645 web applications built with the vibe coding platform Lovable had vulnerabilities that exposed personal information to anyone on the internet. That’s a 10% critical vulnerability rate right out of the box. The real danger: AI-generated code doesn’t look broken. It looks polished, well-structured, and professional. It passes the eyeball test. But underneath those clean variable names, it’s often riddled with security flaws that would make a penetration tester weep with joy. 🔧 Why this matters to me personally: As a security engineer who also writes trading automation, I live in both worlds. My trading system handles real money and real API credentials. Every line of AI-generated code in that system gets the same scrutiny as production security infrastructure. The stakes are too high for “it looks right.” The Top 5 Security Nightmares I’ve Found in Vibed Code After spending the last several months auditing code across different teams, I’ve built up a depressingly predictable list of security issues that LLMs keep introducing. Here are the greatest hits: 1. The “Almost Right” Authentication LLMs love generating auth code that’s 90% correct. JWT validation that checks the signature but skips expiration. OAuth flows that don’t validate the state parameter. Session management that uses predictable tokens. # Vibed code that looks fine but is dangerously broken def verify_token(token: str) -> dict: try: payload = jwt.decode( token, SECRET_KEY, algorithms=["HS256"], # Missing: options={"verify_exp": True} # Missing: audience verification # Missing: issuer verification ) return payload except jwt.InvalidTokenError: raise HTTPException(status_code=401) This code will pass every code review from someone who doesn’t specialize in auth. It decodes the JWT, checks the algorithm, handles the error. But it’s missing critical validation that an attacker will find in about five minutes. 2. SQL Injection Wearing a Disguise Modern LLMs know they should use parameterized queries. So they do—most of the time. But they’ll sneak in string formatting for table names, column names, or ORDER BY clauses where parameterization doesn’t work, and they won’t add any sanitization. # The LLM used parameterized queries... except where it didn't async def get_user_data(user_id: int, sort_by: str): query = f"SELECT * FROM users WHERE id = $1 ORDER BY {sort_by}" # 💀 return await db.fetch(query, user_id) 3. Secrets Hiding in Plain Sight LLMs are trained on millions of code examples that include hardcoded credentials, API keys, and connection strings. When they generate code for you, they often follow the same patterns—embedding secrets directly in configuration files, environment setup scripts, or even in application code with a comment saying “TODO: move to env vars.” 4. Overly Permissive CORS Almost every vibed web application I’ve audited has Access-Control-Allow-Origin: * in production. LLMs default to maximum permissiveness because it “works” and doesn’t generate errors during development. 5. Missing Input Validation Everywhere LLMs generate the happy path beautifully. Form handling, data processing, API endpoints—all functional. But edge cases? Malicious input? File upload validation? These get skipped or half-implemented with alarming consistency. Why LLMs Are Structurally Bad at Security This isn’t just about current limitations that will get fixed in the next model version. There are structural reasons why LLMs struggle with security: They’re trained on average code. The internet is full of tutorials, Stack Overflow answers, and GitHub repos with terrible security practices. LLMs absorb all of it. They generate code that reflects the statistical average of what exists online—and the average is not secure. Security is about absence, not presence. Good security means ensuring that bad things don’t happen. But LLMs are optimized to generate code that does things—that fulfills functional requirements. They’re great at building features, terrible at preventing attacks. Context windows aren’t threat models. A security engineer reviews code with a mental model of the entire attack surface. “If this endpoint is public, and that database stores PII, then we need rate limiting, input validation, and encryption at rest.” LLMs see a prompt and generate code. They don’t think about the attacker who’ll be probing your API at 3 AM. Security insight: The METR study from July 2025 found that experienced open-source developers were actually 19% slower when using AI coding tools—despite believing they were 20% faster. The perceived productivity gain is often an illusion, especially when you factor in the time spent fixing security issues dow


---
## Claude Code Review: My Honest Take After 3 Months

- URL: https://orthogonal.info/claude-code-changed-how-i-ship-code-heres-my-honest-take-after-3-months/
- Date: 2026-02-10
- Category: Tools &amp; Setup
- Summary: Honest review of Claude Code after 3 months of daily use. Covers real productivity gains, limitations, and how it compares to Copilot and Cursor.

Three months ago, I was skeptical. Another AI coding tool? I’d already tried GitHub Copilot, Cursor, and a handful of VS Code extensions that promised to “10x my productivity.” Most of them were glorified autocomplete — helpful for boilerplate, useless for anything that required actual understanding of a codebase. Then I installed Claude Code, and within the first hour, it did something none of the others had done: it read my entire project, understood the architecture, and fixed a bug I’d been ignoring for two weeks. This isn’t a puff piece. I’ve been using Claude Code daily on production projects — Kubernetes deployments, FastAPI services, React dashboards — and I have strong opinions about where it shines and where it still falls short. Let me walk you through what I’ve learned. What Makes Claude Code Different 📌 TL;DR: Three months ago, I was skeptical. Another AI coding tool? I’d already tried GitHub Copilot, Cursor, and a handful of VS Code extensions that promised to “10x my productivity. 🎯 Quick Answer: After 3 months of daily use, Claude Code excels at complex multi-file refactoring and architectural reasoning compared to GitHub Copilot and Cursor. Copilot is better for inline autocomplete, Cursor for IDE integration, but Claude Code handles ambiguous, large-scope tasks most effectively. Most AI coding assistants work at the file level. You highlight some code, ask a question, get an answer. Claude Code operates at the project level. It’s an agentic coding tool that reads your codebase, edits files, runs commands, and integrates with your development tools. It works in your terminal, IDE (VS Code and JetBrains), browser, and even as a desktop app. The key word here is agentic. Unlike a chatbot that answers questions and waits, Claude Code can autonomously explore your codebase, plan changes across multiple files, run tests to verify its work, and iterate until things actually pass. You describe what you want; Claude figures out how to build it. Here’s how I typically start a session: # Navigate to your project cd ~/projects/my-api # Launch Claude Code claude # Ask it something real > explain how authentication works in this codebase That first command is where the magic happens. Claude doesn’t just grep for “auth” — it traces the entire flow from middleware to token validation to database queries. It builds a mental model of your code that persists throughout the session. The Workflows That Actually Save Me Time 1. Onboarding to Unfamiliar Code I recently inherited a Node.js monorepo with zero documentation. Instead of spending a week reading source files, I ran: > give me an overview of this codebase > how do these services communicate? > trace a user login from the API gateway to the database In 20 minutes, I had a better understanding of the architecture than I would have gotten from a week of code reading. Claude identified the service mesh pattern, pointed out the shared protobuf definitions, and even flagged a deprecated authentication path that was still being hit in production. 💡 Pro Tip: When onboarding, start broad and narrow down. Ask about architecture first, then drill into specific components. Claude keeps context across the session, so each question builds on the last. 2. Bug Fixing With Context Here’s where Claude Code absolutely destroys traditional AI tools. Instead of pasting error messages and hoping for the best, you can do this: > I'm seeing a 500 error when users try to reset their password. > The error only happens for accounts created before January 2025. > Find the root cause and fix it. Claude will read the relevant files, check the database migration history, identify that older accounts use a different hashing scheme, and propose a fix — complete with a migration script and updated tests. All in one shot. 3. The Plan-Then-Execute Pattern For complex changes, I’ve adopted a two-phase workflow that dramatically reduces wasted effort: # Phase 1: Plan Mode (read-only, no changes) claude --permission-mode plan > I need to add OAuth2 support. What files need to change? > What about backward compatibility? > How should we handle the database migration? # Phase 2: Execute (switch to normal mode) # Press Shift+Tab to exit Plan Mode > Implement the OAuth flow from your plan. > Write tests for the callback handler. > Run the test suite and fix any failures. Plan Mode is like having a senior architect review your approach before you write a single line of code. Claude reads the codebase with read-only access, asks clarifying questions, and produces a detailed implementation plan. Only when you’re satisfied do you let it start coding. 🔐 Security Note: Plan Mode is especially valuable for security-sensitive changes. I always use it before modifying authentication, authorization, or encryption code. Having Claude analyze the security implications before making changes has caught issues I would have missed. CLAUDE.md — Your Project’s Secret Weapon This is the feature that separates power users from casual users. CLAUDE.md is a special file that Claude reads at the start of every conversation. Think of it as persistent context that tells Claude how your project works, what conventions to follow, and what to avoid. Here’s what mine looks like for a typical project: # Code Style - Use ES modules (import/export), not CommonJS (require) - Destructure imports when possible - All API responses must use the ResponseWrapper class # Testing - Run tests with: npm run test:unit - Always run tests after making changes - Use vitest, not jest # Security - Never commit .env files - All API endpoints must validate JWT tokens - Use parameterized queries — no string interpolation in SQL # Architecture - Services communicate via gRPC, not REST - All database access goes through the repository pattern - Scheduled jobs use BullMQ, not cron The /init command can generate a starter CLAUDE.md by analyzing your project structure. But I’ve found that manually curating it produces much better results. Keep it concise — if it’s too long, Claude starts ignoring rules (just like humans ignore long READMEs). ⚠️ Gotcha: Don’t put obvious things in CLAUDE.md like “write clean code” or “use meaningful variable names.” Claude already knows that. Focus on project-specific conventions that Claude can’t infer from the code itself. Security Configuration — The Part Most People Skip As a security engineer, this is where I get opinionated. Claude Code has a solid permission system, and you should use it. The default “ask for everything” mode is fine for exploration, but for daily use, you want to configure explicit allow/deny rules. Here’s my .claude/settings.json for a typical project: { "permissions": { "allow": [ "Bash(npm run lint)", "Bash(npm run test *)", "Bash(git diff *)", "Bash(git log *)" ], "deny": [ "Read(./.env)", "Read(./.env.*)", "Read(./secrets/**)", "Read(./config/credentials.json)", "Bash(curl *)", "Bash(wget *)", "WebFetch" ] } } The deny rules are critical. By default, Claude can read any file in your project — including your .env files with database passwords, API keys, and secrets. The permission rules above ensure Claude never sees those files, even accidentally. 🚨 Common Mistake: Running claude --dangerously-skip-permissions in a directory with sensitive files. This flag bypasses ALL permission checks. Only use it inside a sandboxed container with no network access and no sensitive data. For even stronger isolation, Claude Code supports OS-level sandboxing that restricts filesystem and network access: { "sandbox": { "enabled": true, "autoAllowBashIfSandboxed": true, "network": { "allowedDomains": ["github.com", "*.npmjs.org"], "allowLocalBinding": true } } } With sandboxing enabled, Claude can work more freely within defined boundaries — no more clicking “approve” for every npm install. Subagents and Parallel Execution One of Claude Code’s most powerful features is subagents — specialized AI assistants that run in their own context window. This


---
## Boost C# ConcurrentDictionary Performance in Kubernetes

- URL: https://orthogonal.info/secure-c-concurrent-dictionary-for-kubernetes/
- Date: 2026-02-10
- Category: DevOps
- Summary: Learn how to optimize C# ConcurrentDictionary for high performance in Kubernetes. Explore thread safety, scalability, and best practices for developers.

Explore a production-grade, security-first approach to using C# Concurrent Dictionary in Kubernetes environments. Learn best practices for scalability and DevSecOps integration. Introduction to C# Concurrent Dictionary 📌 TL;DR: Explore a production-grade, security-first approach to using C# Concurrent Dictionary in Kubernetes environments. Learn best practices for scalability and DevSecOps integration. 🎯 Quick Answer: ConcurrentDictionary in Kubernetes requires tuning concurrencyLevel to match pod CPU limits, not node CPU count. Set initial capacity to expected size to avoid rehashing under load, and use bounded collections with eviction policies to prevent memory pressure that triggers OOMKill in containerized environments. I run 30+ containers in production across my infrastructure, and shared state management is where most subtle bugs hide. After debugging a particularly nasty race condition in a caching layer that took 14 hours to reproduce, I built a set of patterns for ConcurrentDictionary that I now apply to every project. Here’s what I learned. Concurrent Dictionary is a lifesaver for developers dealing with multithreaded applications. Unlike traditional dictionaries, it provides built-in mechanisms to ensure thread safety during read and write operations. This makes it ideal for scenarios where multiple threads need to access and modify shared data simultaneously. Its key features include atomic operations, lock-free reads, and efficient handling of high-concurrency workloads. But as powerful as it is, using it in production—especially in Kubernetes environments—requires careful planning to avoid pitfalls and security risks. One of the standout features of Concurrent Dictionary is its ability to handle millions of operations per second in high-concurrency scenarios. This makes it an excellent choice for applications like caching layers, real-time analytics, and distributed systems. However, this power comes with responsibility. Misusing it can lead to subtle bugs that are hard to detect and fix, especially in distributed environments like Kubernetes. For example, consider a scenario where multiple threads are updating a shared cache of user sessions. Without a thread-safe mechanism, you might end up with corrupted session data, leading to user-facing errors. Concurrent Dictionary eliminates this risk by ensuring that all operations are atomic and thread-safe. 💡 Pro Tip: Use Concurrent Dictionary for scenarios where read-heavy operations dominate. Its lock-free read mechanism ensures minimal performance overhead. Challenges in Production Environments 🔍 From production: A ConcurrentDictionary in one of my services was silently leaking memory—10MB/hour under load. The cause: delegates passed to GetOrAdd were creating closures that captured large objects. Switching to the TryGetValue/TryAdd pattern cut memory growth to near zero. Using Concurrent Dictionary in a local development environment may feel straightforward, but production is a different beast entirely. The stakes are higher, and the risks are more pronounced. Here are some common challenges: Memory Pressure: Concurrent Dictionary can grow unchecked if not managed properly, leading to memory bloat and potential OOMKilled containers in Kubernetes. Thread Contention: While Concurrent Dictionary is designed for high concurrency, improper usage can still lead to bottlenecks, especially under extreme workloads. Security Risks: Without proper validation and sanitization, malicious data can be injected into the dictionary, leading to vulnerabilities like denial-of-service attacks. In Kubernetes, these challenges are amplified. Containers are ephemeral, resources are finite, and the dynamic nature of orchestration can introduce unexpected edge cases. This is why a security-first approach is non-negotiable. Another challenge arises when scaling applications horizontally in Kubernetes. If multiple pods are accessing their own instance of a Concurrent Dictionary, ensuring data consistency across pods becomes a significant challenge. This is especially critical for applications that rely on shared state, such as distributed caches or session stores. For example, imagine a scenario where a Kubernetes pod is terminated and replaced due to a rolling update. If the Concurrent Dictionary in that pod contained critical state information, that data would be lost unless it was persisted or synchronized with other pods. This highlights the importance of designing your application to handle such edge cases. ⚠️ Security Note: Never assume default configurations are safe for production. Always audit and validate your setup. 💡 Pro Tip: Use Kubernetes ConfigMaps or external storage solutions to persist critical state information across pod restarts. Best Practices for Secure Implementation To use Concurrent Dictionary securely and efficiently in production, follow these best practices: 1. Ensure Thread-Safety and Data Integrity Concurrent Dictionary provides thread-safe operations, but misuse can still lead to subtle bugs. Always use atomic methods like TryAdd, TryUpdate, and TryRemove to avoid race conditions. using System.Collections.Concurrent; var dictionary = new ConcurrentDictionary<string, int>(); // Safely add a key-value pair if (!dictionary.TryAdd("key1", 100)) { Console.WriteLine("Failed to add key1"); } // Safely update a value dictionary.TryUpdate("key1", 200, 100); // Safely remove a key dictionary.TryRemove("key1", out var removedValue); Also, consider using the GetOrAdd and AddOrUpdate methods for scenarios where you need to initialize or update values conditionally. These methods are particularly useful for caching scenarios where you want to lazily initialize values. var value = dictionary.GetOrAdd("key2", key => ExpensiveComputation(key)); dictionary.AddOrUpdate("key2", 300, (key, oldValue) => oldValue + 100); 2. Implement Secure Coding Practices Validate all inputs before adding them to the dictionary. This prevents malicious data from polluting your application state. Also, sanitize keys and values to avoid injection attacks. For example, if your application uses user-provided data as dictionary keys, ensure that the keys conform to a predefined schema or format. This can be achieved using regular expressions or custom validation logic. 💡 Pro Tip: Use regular expressions or predefined schemas to validate keys and values before insertion. 3. Monitor and Log Dictionary Operations Logging is an often-overlooked aspect of using Concurrent Dictionary in production. By logging dictionary operations, you can gain insights into how your application is using the dictionary and identify potential issues early. dictionary.TryAdd("key3", 500); Console.WriteLine($"Added key3 with value 500 at {DateTime.UtcNow}"); Integrating Concurrent Dictionary with Kubernetes Running Concurrent Dictionary in a Kubernetes environment requires optimization for containerized workloads. Here’s how to do it: 1. Optimize for Resource Constraints Set memory limits on your containers to prevent uncontrolled growth of the dictionary. Use Kubernetes resource quotas to enforce these limits. apiVersion: v1 kind: Pod metadata: name: concurrent-dictionary-example spec: containers: - name: app-container image: your-app-image resources: limits: memory: "512Mi" cpu: "500m" Also, consider implementing eviction policies for your dictionary to prevent it from growing indefinitely. For example, you can use a custom wrapper around Concurrent Dictionary to evict the least recently used items when the dictionary reaches a certain size. 2. Monitor Performance Leverage Kubernetes-native tools like Prometheus and Grafana to monitor dictionary performance. Track metrics like memory usage, thread contention, and operation latency. 💡 Pro Tip: Use custom metrics to expose dictionary-specific performance data to Prometheus. 3. Handle Pod Restarts Gracefully As mentioned earlier, Kubernetes pods are ephemeral. To handle pod restarts gracefully, con


---
## Home Network Segmentation with OPNsense: A Complete Guide

- URL: https://orthogonal.info/home-network-segmentation-with-opnsense-a-complete-guide/
- Date: 2026-02-05
- Category: Homelab
- Summary: Segment your home network with OPNsense for better security. Complete guide to VLANs, firewall rules, IoT isolation, and guest network configuration.

My homelab has 30+ Docker containers, 4 VLANs, and over a dozen IoT devices—all managed through OPNsense on a Protectli vault. Before I set up segmentation, my smart plugs could ping my NAS and my guest Wi-Fi clients could see every service on my network. This guide walks you through exactly how I segmented everything, step by step. A notable example of this occurred during the Mirai botnet attacks, where unsecured IoT devices like cameras and routers were exploited to launch massive DDoS attacks. The lack of network segmentation allowed attackers to easily hijack multiple devices in the same network, amplifying the scale and damage of the attack. By implementing network segmentation, you can isolate devices into separate virtual networks, reducing the risk of lateral movement and containing potential breaches. we’ll show you how to achieve effective network segmentation using OPNsense, a powerful and open-source firewall solution. Whether you’re a tech enthusiast or a beginner, this step-by-step guide will help you create a safer, more secure home network. What You’ll Learn 📌 TL;DR: In today’s connected world, the average home network is packed with devices ranging from laptops and smartphones to smart TVs, security cameras, and IoT gadgets. While convenient, this growing number of devices also introduces potential security risks. 🎯 Quick Answer: Segment your home network into at least 4 VLANs using OPNsense: trusted devices, IoT, servers/Docker, and guest. Apply firewall rules blocking IoT-to-LAN traffic while allowing LAN-to-IoT management. This isolates compromised IoT devices from reaching sensitive systems even on the same physical network. 🏠 My setup: TrueNAS SCALE · 64GB ECC RAM · dual 10GbE NICs · OPNsense on a Protectli vault · 4 VLANs (IoT, Trusted, DMZ, Guest) · 30+ Docker containers · 60TB+ ZFS storage. Understanding VLANs and their role in network segmentation Planning your home network layout for maximum efficiency and security Setting up OPNsense for VLANs and segmentation Configuring firewall rules to protect your network Setting up DHCP and DNS for segmented networks Configuring your network switch for VLANs Testing and monitoring your segmented network Troubleshooting common issues By the end of this guide, you’ll have a well-segmented home network that enhances both security and performance. Understanding VLANs Virtual Local Area Networks (VLANs) are a powerful way to segment your home network without requiring additional physical hardware. A VLAN operates at Layer 2 of the OSI model, using switches to create isolated network segments. Devices on different VLANs cannot communicate with each other unless a router or Layer 3 switch is used to route the traffic. This segmentation improves network security and efficiency by keeping traffic isolated and reducing unnecessary broadcast traffic. When traffic travels across a network, it can either be tagged or untagged. Tagged traffic includes a VLAN ID (identifier) in its Ethernet frame, following the 802.1Q standard. This tagging allows switches to know which VLAN the traffic belongs to. Untagged traffic, on the other hand, does not include a VLAN tag and is typically assigned to the default VLAN of the port it enters. Each switch port has a Port VLAN ID (PVID) that determines the VLAN for untagged incoming traffic. Switch ports can operate in two main modes: access and trunk. Access ports are configured for a single VLAN and are commonly used to connect end devices like PCs or printers. Trunk ports, on the other hand, carry traffic for multiple VLANs and are used to connect switches or other devices that need to understand VLAN tags. Trunk ports use 802.1Q tagging to identify VLANs for traffic passing through them. Using VLANs is often better than physically separating network segments because it reduces hardware costs and simplifies network management. Instead of buying separate switches for each network segment, you can configure VLANs on a single switch. This flexibility is particularly useful in home networks where you want to isolate devices (like IoT gadgets or guest devices) but don’t have room or budget for extra hardware. Example of VLAN Traffic Flow The following is a simple representation of VLAN traffic flow: Device/Port VLAN Traffic Type Description PC1 (Access Port) 10 Untagged PC1 is part of VLAN 10 and sends traffic untagged. Switch (Trunk Port) 10, 20 Tagged The trunk port carries tagged traffic for VLANs 10 and 20. PC2 (Access Port) 20 Untagged PC2 is part of VLAN 20 and sends traffic untagged. In this example, PC1 and PC2 are on separate VLANs. They cannot communicate with each other unless a router is configured to route traffic between VLANs. ### Planning Your VLAN Layout When setting up a home network, organizing your devices into VLANs (Virtual Local Area Networks) can significantly enhance security, performance, and manageability. VLANs allow you to segregate traffic based on device type or role, ensuring that sensitive devices are isolated while minimizing unnecessary communication between devices. Below is a recommended VLAN layout for a typical home network, along with the associated IP ranges and purposes. #### Recommended VLAN Layout 1. **VLAN 10: Management** (10.0.10.0/24) This VLAN is dedicated to managing your network infrastructure, such as your router (e.g., OPNsense), managed switches, and wireless access points (APs). Isolating management traffic ensures that only authorized devices can access critical network components. 2. **VLAN 20: Trusted** (10.0.20.0/24) This is the primary VLAN for everyday devices such as workstations, laptops, and smartphones. These devices are considered trusted, and this VLAN has full internet access. Inter-VLAN communication with other VLANs should be carefully restricted. 3. **VLAN 30: IoT** (10.0.30.0/24) IoT devices, such as smart home assistants, cameras, and thermostats, often have weaker security and should be isolated from the rest of the network. Restrict inter-VLAN access for these devices, while allowing them to access the internet as needed. 4. **VLAN 40: Guest** (10.0.40.0/24) This VLAN is for visitors who need temporary WiFi access. It should provide internet connectivity while being completely isolated from the rest of your network to protect your devices and data. 5. **VLAN 50: Lab/DMZ** (10.0.50.0/24) If you experiment with homelab servers, development environments, or host services exposed to the internet, this VLAN is ideal. Isolating these devices minimizes the risk of security breaches affecting other parts of the network. Below is an HTML table for a quick reference of the VLAN layout: “`html VLAN ID Name Subnet Purpose Internet Access Inter-VLAN Access 10 Management 10.0.10.0/24 OPNsense, switches, APs Limited Restricted 20 Trusted 10.0.20.0/24 Workstations, laptops, phones Full Restricted 30 IoT 10.0.30.0/24 Smart home devices, cameras Full Restricted 40 Guest 10.0.40.0/24 Visitor WiFi Full None 50 Lab/DMZ 10.0.50.0/24 Homelab servers, exposed services Full Restricted “` 1. Creating VLAN Interfaces To start, navigate to Interfaces > Other Types > VLAN. This is where you will define your VLANs on a parent interface, typically igb0 or em0. Follow these steps: Click Add (+) to create a new VLAN. In the Parent Interface dropdown, select the parent interface (e.g., igb0). Enter the VLAN tag (e.g., 10 for VLAN 10). Provide a Description (e.g., “VLAN10_Office”). Click Save. Repeat the above steps for each VLAN you want to create. Parent Interface: igb0 VLAN Tag: 10 Description: VLAN10_Office 2. Assigning VLAN Interfaces Once VLANs are created, they must be assigned as interfaces. Go to Interfaces > Assignments and follow these steps: In the Available Network Ports dropdown, locate the VLAN you created (e.g., igb0_vlan10). Click Add. Rename the interface (e.g., “VLAN10_Office”) for easier identification. Click Save. 3. Configuring Interface IP Addresses After assigning VLAN interfaces, configure IP addre


---
## Risk Management & Position Sizing for Traders

- URL: https://orthogonal.info/risk-management-position-sizing-an-engineers-guide-to-trading/
- Date: 2026-02-05
- Category: Finance &amp; Trading
- Summary: Master risk management and position sizing to protect your trading capital. Covers Kelly criterion, volatility-based sizing, and max drawdown strategies.

I blew up a paper trading account in my first month of algorithmic trading. Not because my signals were wrong—my position sizing was. I’ve since built automated risk management into every layer of my Python trading system, from Kelly Criterion calculations to real-time drawdown monitoring. Here’s the framework that keeps my capital intact. Trading isn’t just about picking winners; it’s about surviving the losers. Without a structured approach to managing risk, even the best strategies can fail. As engineers, we thrive on systems, optimization, and logic—qualities that are invaluable in trading. This guide will show you how to apply engineering principles to trading risk management and position sizing, ensuring you stay in the game long enough to win. Table of Contents 📌 TL;DR: Picture this: You’ve spent weeks analyzing market trends, backtesting strategies, and finally, you pull the trigger on a trade. It’s a winner—your portfolio grows by 10%. You’re feeling invincible. 🎯 Quick Answer: Use the Kelly Criterion to calculate optimal position size based on win rate and reward-to-risk ratio, then apply a fractional Kelly (25–50%) to reduce drawdown risk. Never risk more than 1–2% of total capital per trade, and implement automated drawdown monitoring to halt trading at predefined loss thresholds. Kelly Criterion Position Sizing Methods Maximum Drawdown Value at Risk Stop-Loss Strategies Portfolio Risk Risk-Adjusted Returns Risk Management Checklist FAQ The Kelly Criterion 📊 Real example: My system flagged a high-conviction trade on a biotech stock—Kelly Criterion suggested 18% allocation. I capped it at 5% per my hard rules. The trade went against me 12% before reversing. Without the position cap, that single trade would have wiped 2% of total capital instead of the 0.6% actual loss. The Kelly Criterion is a mathematical formula that calculates the best bet size to maximize long-term growth. It’s widely used in trading and gambling to balance risk and reward. Here’s the formula: f* = (bp - q) / b Where: f*: Fraction of capital to allocate to the trade b: Odds received on the trade (net return per dollar wagered) p: Probability of winning the trade q: Probability of losing the trade (q = 1 - p) Worked Example Imagine a trade with a 60% chance of success (p = 0.6) and odds of 2:1 (b = 2). Using the Kelly formula: f* = (2 * 0.6 - 0.4) / 2 f* = 0.4 According to the Kelly Criterion, you should allocate 40% of your capital to this trade. ⚠️ Gotcha: The Kelly Criterion assumes precise knowledge of probabilities and odds, which is rarely available in real-world trading. Overestimating p or underestimating q can lead to over-betting and catastrophic losses. Full Kelly vs Fractional Kelly While the Full Kelly strategy uses the exact fraction calculated, it can lead to high volatility. Many traders prefer fractional approaches: Half Kelly: Use 50% of the f* value Quarter Kelly: Use 25% of the f* value For example, if f* = 0.4, Half Kelly would allocate 20% of capital, and Quarter Kelly would allocate 10%. These methods reduce volatility and better handle estimation errors. Python Implementation Here’s a Python implementation of the Kelly Criterion: def calculate_kelly(b, p): q = 1 - p # Probability of losing return (b * p - q) / b # Example usage b = 2 # Odds (2:1) p = 0.6 # Probability of winning (60%) full_kelly = calculate_kelly(b, p) half_kelly = full_kelly / 2 quarter_kelly = full_kelly / 4 print(f"Full Kelly Fraction: {full_kelly}") print(f"Half Kelly Fraction: {half_kelly}") print(f"Quarter Kelly Fraction: {quarter_kelly}") 💡 Pro Tip: Use conservative estimates for p and q to avoid over-betting. Fractional Kelly is often a safer choice for volatile markets. Position Sizing Methods Position sizing determines how much capital to allocate to a trade. It’s a cornerstone of risk management, ensuring you don’t risk too much on a single position. Here are four popular methods: 1. Fixed Dollar Method Risk a fixed dollar amount per trade. For example, if you risk $100 per trade, your position size depends on the stop-loss distance. def fixed_dollar_size(risk_per_trade, stop_loss): return risk_per_trade / stop_loss # Example usage print(fixed_dollar_size(100, 2)) # Risk $100 with $2 stop-loss Pros: Simple and consistent.Cons: Does not scale with account size or volatility. 2. Fixed Percentage Method Risk a fixed percentage of your portfolio per trade (e.g., 1% or 2%). This method adapts to account growth and prevents large losses. def fixed_percentage_size(account_balance, risk_percentage, stop_loss): risk_amount = account_balance * (risk_percentage / 100) return risk_amount / stop_loss # Example usage print(fixed_percentage_size(10000, 2, 2)) # 2% risk of $10,000 account with $2 stop-loss Pros: Scales with account size.Cons: Requires frequent recalculation. 3. Volatility-Based (ATR Method) Uses the Average True Range (ATR) indicator to measure market volatility. Position size is calculated as risk amount divided by ATR value. def atr_position_size(risk_per_trade, atr_value): return risk_per_trade / atr_value # Example usage print(atr_position_size(100, 1.5)) # Risk $100 with ATR of 1.5 Pros: Adapts to market volatility.Cons: Requires ATR calculation. 4. Fixed Ratio (Ryan Jones Method) Scale position size based on profit milestones. For example, increase position size after every $500 profit. def fixed_ratio_size(initial_units, account_balance, delta): return (account_balance // delta) + initial_units # Example usage print(fixed_ratio_size(1, 10500, 500)) # Start with 1 unit, increase per $500 delta Pros: Encourages disciplined scaling.Cons: Requires careful calibration of milestones. Maximum Drawdown 🔧 Why I hardcoded these limits: My trading system enforces position limits at the code level—no trade can exceed 5% of portfolio value, and the system auto-liquidates if drawdown hits 15%. You can’t override it in the heat of the moment, which is exactly the point. Maximum Drawdown (MDD) measures the largest peak-to-trough decline in portfolio value. It’s a critical metric for understanding risk. def calculate_max_drawdown(equity_curve): peak = equity_curve[0] max_drawdown = 0 for value in equity_curve: if value > peak: peak = value drawdown = (peak - value) / peak max_drawdown = max(max_drawdown, drawdown) return max_drawdown # Example usage equity_curve = [100, 120, 90, 80, 110] print(f"Maximum Drawdown: {calculate_max_drawdown(equity_curve)}") 🔐 Security Note: Recovery from drawdowns is non-linear. A 50% loss requires a 100% gain to break even. Always aim to minimize drawdowns to preserve capital. Value at Risk (VaR) Value at Risk estimates the potential loss of a portfolio over a specified time period with a given confidence level. Historical VaR Calculates potential loss based on historical returns. def calculate_historical_var(returns, confidence_level): sorted_returns = sorted(returns) index = int((1 - confidence_level) * len(sorted_returns)) return -sorted_returns[index] # Example usage portfolio_returns = [-0.02, -0.01, 0.01, 0.02, -0.03, 0.03, -0.04] confidence_level = 0.95 print(f"Historical VaR: {calculate_historical_var(portfolio_returns, confidence_level)}") Python Implementation: Building Your Own Position Sizer Theory is great, but I learn by building. Here are the three tools I actually use in my trading workflow, all written in Python. These aren’t toy examples—I run variations of these scripts before every trade. Kelly Criterion Calculator The Kelly formula tells you the optimal fraction of your bankroll to bet. In practice, I always use a fractional Kelly (typically half-Kelly) because full Kelly is far too aggressive for real accounts with correlated positions and fat-tailed distributions. def kelly_criterion(win_rate, avg_win, avg_loss, fraction=0.5): """Calculate Kelly Criterion position size. Args: win_rate: Historical win rate (0.0 to 1.0) avg_win: Average winning trade return (e.g., 0.03 for 3%) avg_loss: Average losing trade return (e.g., 0.0


---
## Threat Modeling Made Simple for Developers

- URL: https://orthogonal.info/threat-modeling-made-simple-for-developers/
- Date: 2026-02-05
- Category: Security
- Summary: Learn threat modeling as a developer with practical, simplified techniques. Covers STRIDE, data flow diagrams, risk scoring, and actionable mitigations.

I run a threat model for every new service I deploy—whether it’s a Kubernetes workload on my homelab or an API headed to production. It doesn’t have to be a week-long exercise. Here’s the simplified process I use that takes an afternoon and catches the issues that actually matter. In today’s complex digital landscape, software security is no longer an afterthought—it’s a critical component of successful development. Threat modeling, the process of identifying and addressing potential security risks, is a skill that every developer should master. Why? Because understanding the potential vulnerabilities in your application early in the development lifecycle can mean the difference between a secure application and a costly security breach. As a developer, knowing how to think like an attacker not only makes your solutions stronger but also helps you grow into a more versatile and valued professional. Threat modeling is not just about identifying risks—it’s about doing so at the right time. Studies show that addressing security issues during the design phase can save up to 10 times the cost of fixing the same issue in production. Early threat modeling helps you build security into your applications from the ground up, avoiding expensive fixes, downtime, and potential reputational damage down the road. we break down the fundamentals of threat modeling in a way that is approachable for developers of all levels. You’ll learn about popular frameworks like STRIDE and DREAD, how to use attack trees, and a straightforward 5-step process to implement threat modeling in your workflow. We’ll also provide practical examples, explore some of the best tools available, and highlight common mistakes to avoid. By the end of this article, you’ll have the confidence and knowledge to make your applications more secure. Table of Contents 📌 TL;DR: In today’s complex digital landscape, software security is no longer an afterthought—it’s a critical component of successful development. Threat modeling, the process of identifying and addressing potential security risks, is a skill that every developer should master. 🎯 Quick Answer: Simplified threat modeling takes one afternoon using a four-step process: map data flows, identify trust boundaries, enumerate threats with STRIDE, and prioritize by impact and likelihood. This lightweight approach catches the critical security issues that matter without requiring weeks of formal analysis. STRIDE Framework DREAD Model Attack Trees The 5-Step Process Practical Example Recommended Tools Common Mistakes FAQ ### STRIDE Methodology: A Complete Breakdown The STRIDE methodology is a threat modeling framework developed by Microsoft to help identify and mitigate security threats in software systems. It categorizes threats into six distinct types: Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, and Elevation of Privilege. Below, we dig into each category with concrete examples relevant to web applications and suggested mitigation strategies. — #### 1. **Spoofing** Spoofing refers to impersonating another entity, such as a user or process, to gain unauthorized access to a system. In web applications, spoofing often manifests as identity spoofing or authentication bypass. – **Example**: An attacker uses stolen credentials or exploits a weak authentication mechanism to log in as another user. – **Mitigation**: Implement multi-factor authentication (MFA), secure password policies, and robust session management to prevent unauthorized access. — #### 2. **Tampering** Tampering involves modifying data or system components to manipulate how the system functions. In web applications, this threat is often seen in parameter manipulation or data injection. – **Example**: An attacker alters query parameters in a URL (e.g., changing `price=50` to `price=1`) to manipulate application behavior. – **Mitigation**: Use server-side validation, cryptographic hashing for data integrity, and secure transport protocols like HTTPS. — #### 3. **Repudiation** Repudiation occurs when an attacker performs an action and later denies it, exploiting inadequate logging or auditing mechanisms. – **Example**: A user deletes sensitive logs or alters audit trails to hide malicious activities. – **Mitigation**: Implement tamper-proof logging mechanisms and ensure logs are securely stored and timestamped. Use tools to detect and alert on log modifications. — #### 4. **Information Disclosure** This threat involves exposing sensitive information to unauthorized parties. It can occur due to poorly secured systems, verbose error messages, or data leaks. – **Example**: A web application exposes full database stack traces in error messages, leaking sensitive information like database schema or credentials. – **Mitigation**: Avoid verbose error messages, implement data encryption at rest and in transit, and use role-based access controls to restrict data visibility. — #### 5. **Denial of Service (DoS)** Denial of Service involves exhausting system resources, rendering the application unavailable for legitimate users. – **Example**: An attacker sends an overwhelming number of HTTP requests to the server, causing legitimate requests to time out. – **Mitigation**: Implement rate limiting, CAPTCHAs, and distributed denial-of-service (DDoS) protection techniques such as traffic filtering and load balancing. — #### 6. **Elevation of Privilege** This occurs when an attacker gains higher-level permissions than they are authorized for, often through exploiting poorly implemented access controls. – **Example**: A user modifies their own user ID in a request to access another user’s data (Insecure Direct Object Reference, or IDOR). – **Mitigation**: Enforce strict role-based access control (RBAC) and validate user permissions for every request on the server side. — ### Summary Table (HTML) “`html Threat Description Example Mitigation Spoofing Impersonating another entity (e.g., authentication bypass). An attacker uses stolen credentials to access a user account. Implement MFA, secure password policies, and session management. Tampering Modifying data or parameters to manipulate system behavior. An attacker changes query parameters to lower product prices. Use server-side validation, HTTPS, and cryptographic hashing. Repudiation Denying the performance of an action, exploiting weak logging. A user tampers with logs to erase records of malicious activity. Implement secure, tamper-proof logging mechanisms. Information Disclosure Exposing sensitive information to unauthorized entities. Error messages reveal database schema or credentials. Use encryption, hide sensitive error details, and enforce RBAC. Denial of Service Exhausting resources to make the system unavailable. An attacker floods the server with HTTP requests. Implement rate limiting, CAPTCHAs, and DDoS protection. Elevation of Privilege Gaining unauthorized higher-level permissions. A user accesses data belonging to another user via IDOR. Enforce RBAC and validate permissions on the server side. “` The STRIDE framework provides a systematic approach to identifying and addressing security threats. By understanding these categories and implementing appropriate mitigations, developers can build more secure web applications. DREAD Risk Scoring DREAD is a risk assessment model used to evaluate and prioritize threats based on five factors: Damage: Measures the potential impact of the threat. How severe is the harm if exploited? Reproducibility: Determines how easily the threat can be replicated. Can an attacker consistently exploit the same vulnerability? Exploitability: Evaluates the difficulty of exploiting the threat. Does the attacker require special tools, skills, or circumstances? Affected Users: Assesses the number of users impacted. Is it a handful of users or the entire system? Discoverability: Rates how easy it is to find the vulnerability. Can it be detected with automated tools or is manual inspection requi


---
## Solving Homelab Bottlenecks: Why Upgrading to a 2.5G

- URL: https://orthogonal.info/i-spent-800-on-2-5g-gear-but-forgot-one-50-component/
- Date: 2026-01-25
- Category: Tools &amp; Setup
- Summary: Upgrading to 2.5GbE revealed hidden homelab bottlenecks. Learn how one overlooked component can throttle your entire network and how to diagnose it.

A Costly Oversight: Lessons from My Homelab Upgrade 📌 TL;DR: A Costly Oversight: Lessons from My Homelab Upgrade Imagine spending $800 upgrading your homelab network, only to discover that one overlooked component reduced all your shiny new hardware to a fraction of its potential. 🎯 Quick Answer: A $50 Cat5e patch cable or unmanaged switch can bottleneck an $800 2.5GbE network upgrade to 1Gbps. Always verify every component in the chain—cables, switches, NICs, and router ports—supports 2.5GbE before assuming the upgrade is complete. 🏠 My setup: 10GbE between TrueNAS and switch · 2.5GbE to all workstations · TrueNAS SCALE · 64GB ECC RAM · 60TB+ ZFS storage · OPNsense firewall. After saturating my 1GbE links with ZFS replication and nightly backups between my TrueNAS SCALE server and offsite NAS, I knew it was time to upgrade. My 60TB+ of data wasn’t going to back up itself any faster over Gigabit. Here’s how the 2.5GbE upgrade changed everything—and the one $50 mistake that almost ruined it. Here’s how it all started: a new Synology NAS with 2.5GbE ports, a WiFi 6 router with multi-gig backhaul, and a 2.5G PCIe NIC for my workstation. Everything was in place for faster local file transfers—or so I thought. But my first big test—copying a 60GB photo library to the NAS—produced speeds capped at 112 MB/s. That’s the exact throughput of a Gigabit connection. After much head-scratching and troubleshooting, I realized my old 5-port Gigabit switch was bottlenecking my entire setup. A $50 oversight had rendered my $800 investment nearly pointless. The Gigabit Bottleneck: Why It Matters Homelab enthusiasts often focus on the specs of NAS devices, routers, and workstations, but the network switch—the component connecting everything—is frequently overlooked. If your switch maxes out at 1Gbps, it doesn’t matter if your other devices support 2.5GbE or even 10GbE. The switch becomes the choke point, throttling your network at its weakest link. Here’s how this bottleneck impacts performance: Modern NAS devices with 2.5GbE ports can theoretically transfer data at 295 MB/s. A Gigabit switch limits this to just 112 MB/s. WiFi 6 routers with multi-gig backhaul can push 2.4Gbps or more, but a Gigabit switch throttles them to under 1Gbps. Even affordable 2.5G PCIe NICs (available for under $20) are wasted if your switch can’t keep up with their capabilities. Running multiple simultaneous workloads—such as streaming 4K content while transferring files—suffers significant slowdowns with a Gigabit switch, as it cannot handle the combined bandwidth demands. Pro Tip: Upgrading to a multi-gig switch doesn’t just improve single-device speeds—it unlocks better multi-device performance. Say goodbye to buffering while streaming 4K Plex content or transferring large files simultaneously. Choosing the Right 2.5G Switch Once I realized the problem, I started researching 2.5GbE switches. My requirements were simple: affordable, quiet, and easy to use. However, I was quickly overwhelmed by the variety of options available. Enterprise-grade switches offered incredible features like managed VLANs and 10G uplinks, but they were pricey and noisy—far beyond what my homelab needed. After comparing dozens of options, I landed on the NICGIGA 6-Port 2.5G Unmanaged Switch. It was quiet, affordable, and had future-proof capabilities, including two 10G SFP+ ports for potential upgrades. Key Criteria for Selecting a Switch Here’s what I looked for during my search: 1. Port Configuration A mix of 2.5GbE Base-T ports and 10G SFP+ ports was ideal. The 2.5GbE ports supported my NAS, workstation, and WiFi 6 access point, while the SFP+ ports provided an upgrade path for future 10GbE devices or additional connections. 2. Fanless Design Fan noise in a homelab can be a dealbreaker, especially if it’s near a home office. Many enterprise-grade switches include active cooling systems, which can be noisy. Instead, I prioritized a fanless switch that uses passive cooling. The NICGIGA switch operates silently, even under heavy loads. 3. Plug-and-Play Simplicity I wanted an unmanaged switch—no web interface, no VLAN configuration, no firmware updates to worry about. Just plug in the cables, power it on, and let it do its job. This simplicity made the NICGIGA a perfect fit for my homelab. 4. Build Quality Durability is essential for hardware in a homelab. The NICGIGA switch features a sturdy metal casing that not only protects its internal components but also provides better heat dissipation. Also, its build quality gave me peace of mind during frequent thunderstorms, as it’s resistant to power surges. 5. Switching Capacity A switch’s backplane bandwidth determines how much data it can handle across all its ports simultaneously. The NICGIGA boasts a 60Gbps switching capacity, ensuring that every port can operate at full speed without bottlenecks, even during multi-device workloads. Installing and Testing the Switch Setting up the new switch was straightforward: Unplugged the old Gigabit switch and labeled the Ethernet cables for easier reconnection. Mounted the new switch on my wall-mounted rack using the included hardware. Connected the power adapter and verified that the switch powered on. Reconnected the Ethernet cables to the 2.5GbE ports, ensuring proper placement for devices like my NAS and workstation. Observed the LEDs on the switch to verify link speeds. Green indicated 2.5GbE, while orange indicated Gigabit connections. Within minutes, my network was upgraded. The speed difference was immediately noticeable during file transfers and streaming sessions. Before vs. After: Performance Metrics Here’s how my network performed before and after upgrading: Metric Gigabit Switch 2.5GbE Switch Transfer Speed 112 MB/s 278 MB/s 50GB File Transfer Time 7m 26s 3m 0s Streaming Plex 4K Occasional buffering Smooth playback Multi-device Load Noticeable slowdown No impact ⚠️ What went wrong for me: I spent an hour troubleshooting why my workstation was stuck at 1Gbps after the switch upgrade. Turns out my Cat5 patch cables couldn’t handle 2.5GbE—they looked fine but were only rated for 100MHz. Swapping to Cat6 cables instantly jumped me to full 2.5Gbps. Check your cables before you blame the hardware. Common Pitfalls and Troubleshooting Upgrading to multi-gig networking isn’t always plug-and-play. Here are some common issues and their solutions: Problem: Device only connects at Gigabit speed.Solution: Check if the Ethernet cable supports Cat5e or higher. Older cables may not handle 2.5Gbps. Problem: SFP+ port doesn’t work.Solution: Ensure the module is compatible with your switch. Some switches only support specific brands of SFP+ modules. Problem: No improvement in transfer speed.Solution: Verify your NIC settings. Some network cards default to 1Gbps unless manually configured. # Example: Setting NIC speed to 2.5Gbps in Linux sudo ethtool -s eth0 speed 2500 duplex full autoneg on Pro Tip: Use diagnostic tools like iperf3 to test network throughput. It provides detailed insights into your connection speeds and latency. Future-Proofing with SFP+ Ports The two 10G SFP+ ports on my switch are currently connected to 2.5G modules, but they offer a clear upgrade path to 10GbE. Here’s why they’re valuable: Support for 10G modules allows seamless upgrades. Backward compatibility with 1G and 2.5G modules ensures flexibility. Fiber optic SFP+ modules enable long-distance connections, useful for larger homelabs or network setups in separate rooms. When 10GbE hardware becomes affordable, I’ll already have the infrastructure in place for the next big leap. Quick Summary Old Gigabit switches are often the bottleneck in modern homelabs. Upgrading to 2.5GbE unlocks noticeable performance improvements. The NICGIGA 6-Port 2.5G Unmanaged Switch offers the ideal balance of affordability, simplicity, and future-proofing. Double-check device compatibility before upgrading—your NAS, router, and workstation need to support 2.5GbE. Use quality Et


---
## How to Protect Your Homelab from Dust: A Practical Guide

- URL: https://orthogonal.info/how-dust-almost-killed-my-homelab/
- Date: 2026-01-24
- Category: War Stories
- Summary: Protect your homelab hardware from dust damage with practical filtration, airflow management, and maintenance tips. Prevent overheating and extend uptime.

The Night Dust Almost Took Down My Homelab 📌 TL;DR: The Night Dust Almost Took Down My Homelab It was a quiet night—or so I thought. I was deep in REM sleep when my phone jolted me awake with an ominous notification: Proxmox Critical Errors . Bleary-eyed and half-conscious, I dragged myself to my server rack, bracing for the worst. 🎯 Quick Answer: Dust accumulation caused Proxmox thermal throttling and critical hardware errors in a homelab. Prevent damage with filtered intake fans, positive air pressure cases, quarterly compressed-air cleaning, and monitoring CPU temperatures with alerts above 70°C to catch buildup before it causes failures. 🏠 My setup: TrueNAS SCALE in a rack-mounted chassis · 64GB ECC RAM · dual 10GbE NICs · UPS-protected · OPNsense on a Protectli vault · positive-pressure airflow with filtered intakes. My TrueNAS SCALE server runs 24/7 in a closet—64GB of ECC RAM, dual 10GbE NICs, and 60TB+ of spinning rust that generates real heat. One night I woke up to thermal warnings: CPU temps had spiked 20°C above normal. The culprit wasn’t a hardware failure—it was a thick blanket of dust choking every fan and heatsink in the chassis. That was my wake-up call (literally) to take dust management seriously. I rebooted. No luck. Swore at it. Still nothing. Frantically Googled. Nada. Was my hardware failing? Was my Proxmox setup cursed? The answer, as it turned out, was far simpler and far more maddening: dust. Warning: Dust is not just a nuisance—it’s a silent hardware killer. Ignoring it can lead to thermal throttling, system instability, and even permanent damage. If you’ve ever felt the heart-stopping anxiety of a homelab failure, sit back. I’m here to share the lessons learned, the solutions discovered, and the practical steps you can take to prevent dust-induced chaos in your setup. Why Dust Is a Homelab’s Worst Enemy Dust in a homelab isn’t just an eyesore—it’s a slow, insidious threat to your hardware. With cooling fans spinning around the clock, your server rack essentially operates as a vacuum cleaner, sucking in particles from the surrounding environment. Over time, these particles accumulate, forming layers that blanket your components like insulation. Unfortunately, this “insulation” traps heat instead of dissipating it, leading to overheating and hardware failure. Here are the telltale signs that dust might be wreaking havoc on your homelab: Fans are louder than usual, struggling to push air through clogged filters and heatsinks. System instability, including unexplained crashes, kernel panics, and error messages. Components running unusually hot, with CPU and GPU temperatures spiking. A faint burning smell, signaling that your hardware is under thermal duress. Left unchecked, dust can cause permanent damage, particularly to sensitive components like CPUs, GPUs, and motherboards. Let’s talk about how to stop it before it gets to that point. How Dust Affects Hardware Longevity To understand the power of dust over hardware, it’s essential to break down its impact over time: Thermal Throttling When dust builds up on heatsinks and fans, it reduces their ability to dissipate heat effectively. As a result, components like your CPU and GPU begin to throttle their performance to avoid overheating. This throttling, while protective, significantly reduces the efficiency of your servers, slowing down processes and making workloads take longer than they should. Short-Circuit Risks Dust particles can retain moisture and, over time, become conductive. In extreme cases, this can lead to short circuits on your motherboard or power supply unit (PSU). These kinds of failures often come without warning and can be catastrophic for your homelab setup. Fan Motor Wear Excessive dust buildup forces fans to work harder to push air through the system, leading to wear and tear on the fan motors. Over time, this can cause fans to fail entirely, leaving your system vulnerable to heat damage. Corrosion Dust can carry chemicals or salts from the environment, which can react with metal components inside your servers. While this process is slow, the corrosion it causes can gradually degrade the integrity of your hardware. The cumulative effect of these issues is a dramatic reduction in the lifespan of your equipment, making preventative measures all the more critical. ⚠️ What went wrong for me: I used to clean my servers with a vacuum cleaner—until I learned the hard way that vacuums generate static electricity. One cleaning session killed a NIC. Now I only use compressed air (held upright to avoid moisture), and I always wear an anti-static wrist strap. The replacement NIC cost more than a year’s supply of compressed air cans. How to Prevent Dust Buildup in Your Homelab Preventing dust buildup requires a combination of proactive maintenance and environmental controls. Here’s my battle-tested process: Step 1: Regular Cleaning Dust doesn’t disappear on its own. Commit to a quarterly cleaning schedule to keep your homelab in top shape. Here’s how: Power down and unplug all equipment before cleaning. Open each server case and inspect for dust buildup on fans, heatsinks, and circuit boards. Use compressed air to blow out dust, holding the can upright to avoid spraying moisture. Always wear a mask and use an anti-static wrist strap to protect both yourself and the components. Wipe down external surfaces with a microfiber cloth. Pro Tip: Avoid using vacuum cleaners inside your server cases—they can generate static electricity and damage sensitive components. Step 2: Optimize Airflow Good airflow reduces dust accumulation. Position your servers in a way that ensures clean air intake and efficient exhaust. Use high-quality dust filters on intake fans and clean them regularly. Here’s a Python script to monitor CPU temperatures and alert you when they exceed safe thresholds: import psutil import smtplib from email.mime.text import MIMEText def send_alert(temp): sender = '[email protected]' recipient = '[email protected]' subject = f'CPU Temperature Alert: {temp}°C' body = f'Your CPU temperature has exceeded the safe limit: {temp}°C. Check your server immediately!' msg = MIMEText(body) msg['Subject'] = subject msg['From'] = sender msg['To'] = recipient with smtplib.SMTP('smtp.example.com', 587) as server: server.starttls() server.login(sender, 'your_password') server.send_message(msg) while True: temp = psutil.sensors_temperatures()['coretemp'][0].current if temp > 80: # Adjust threshold as needed send_alert(temp) Run this script on a monitoring device to catch temperature spikes before they cause damage. Step 3: Invest in Air Purification Even with regular cleaning, the environment itself might be contributing to dust buildup. This is where air purifiers come in. After extensive research, I discovered TPA (Two-Polar Active) technology. Unlike HEPA filters, which passively trap dust, TPA actively captures particles using an electric field, storing them on reusable plates. Benefits of TPA technology for homelabs: Captures ultrafine particles down to 0.0146μm—smaller than most HEPA filters can handle. Reusable collector plates eliminate replacement costs. Minimal airflow resistance ensures consistent cooling for your servers. Silent operation means no more background noise competing with your thoughts. Common Pitfalls and Troubleshooting While dust control is critical, it’s easy to make mistakes. Here are some pitfalls to watch out for: Overusing compressed air: Blasting air too close to components can damage delicate parts. Keep the nozzle at least 6 inches away. Skipping airflow optimization: Poor airflow creates hotspots, which accelerate dust buildup and overheating. Neglecting temperature monitoring: Without real-time alerts, you might not notice overheating until it’s too late. Misplacing air purifiers: Place them near server intake vents for maximum effectiveness, but keep them far enough away to avoid electromagnetic interference (EMI). Six Months of


---
## Homelab Hardware Guide: Build Your Dream Setup 2026

- URL: https://orthogonal.info/ultimate-homelab-hardware-guide-self-hosting-made-simple-in-2026/
- Date: 2026-01-15
- Category: Tools &amp; Setup
- Summary: Build your dream homelab in 2026 with this definitive hardware guide. Covers servers, switches, NAS, UPS, and networking gear for self-hosting enthusiasts.

Why Every Tech Enthusiast Needs a Homelab 📌 TL;DR: Why Every Tech Enthusiast Needs a Homelab Picture this: you’re streaming your favorite movie from your personal media server, your smart home devices are smoothly automated, and your development environment is running on hardware you control—all without relying on third-party services. 🎯 Quick Answer: For a 2026 homelab, start with a mini PC like Intel NUC or used Dell Micro (under $300) for low power draw, add a NAS with ECC RAM for data integrity, and run Proxmox or TrueNAS for virtualization. Budget $500–$1,500 for a capable starter setup. My homelab started with a single Raspberry Pi running Pi-hole. Today it’s a TrueNAS SCALE server with 64GB of ECC RAM, dual 10GbE NICs, and 60TB+ of ZFS storage running 30+ Docker containers. I’ve made plenty of expensive mistakes along the way—buying consumer gear I had to replace, undersizing my UPS, skipping ECC RAM to save $40. This guide is everything I wish someone had told me before I started. 🏠 My setup: TrueNAS SCALE on a custom build · 64GB ECC RAM · dual 10GbE NICs · 60TB+ ZFS storage · OPNsense on a Protectli vault · UPS-protected · 30+ Docker containers. But here’s the catch: building a homelab can be overwhelming. With endless hardware options and configurations, where do you even start? Whether you’re a beginner or a seasoned pro, this guide will walk you through every step, from entry-level setups to advanced configurations. Let’s dive in. 💡 Pro Tip: Start small and scale as your needs grow. Over-engineering your setup from day one can lead to wasted resources and unnecessary complexity. Step 1: Entry-Level Hardware for Beginners If you’re new to homelabs, starting with entry-level hardware is the smartest move. It’s cost-effective, simple to set up, and versatile enough to handle a variety of tasks. The Raspberry Pi Revolution The Raspberry Pi 5 is a big improvement in the world of single-board computers. With its quad-core processor, USB 3.0 support, and gigabit Ethernet, it’s perfect for running lightweight services like Pi-hole (network-wide ad-blocking), Home Assistant (smart home automation), or even a small web server. # Install Docker on Raspberry Pi 5 curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER # Run a lightweight web server docker run -d -p 8080:80 nginx With a power consumption of less than 15 watts, the Raspberry Pi 5 is an energy-efficient choice. Pair it with a high-quality microSD card or an external SSD for storage. If you’re feeling adventurous, you can even cluster multiple Raspberry Pis to create a Kubernetes lab for container orchestration experiments. ⚠️ Gotcha: Avoid using cheap, generic power supplies with your Raspberry Pi. Voltage fluctuations can cause instability and hardware damage. Stick to the official power supply for reliable performance. Other single-board computers like the Odroid N2+ or RockPro64 are excellent alternatives if you need more RAM or CPU power. These devices offer similar functionality with added expandability, making them ideal for slightly more demanding workloads. ⚠️ What went wrong for me: My first UPS was undersized—a cheap 600VA unit. During a 15-minute power outage, it drained in 8 minutes because I forgot to account for the switch, NAS, and OPNsense firewall all drawing power simultaneously. I upgraded to a 1500VA unit with a network management card so TrueNAS can trigger a clean shutdown automatically. Size your UPS for your entire rack, not just the server. Step 2: Centralized Storage for Your Data As your homelab grows, you’ll quickly realize the importance of centralized storage. A Network Attached Storage (NAS) system is the backbone of any homelab, providing a secure and organized way to store, share, and back up your data. Choosing the Right NAS The Synology DS224+ NAS is a fantastic choice for beginners and pros alike. With support for up to 32TB of storage, hardware encryption, and Docker container integration, it’s perfect for hosting a Plex media server or automating backups. # Set up a shared folder on a Synology NAS ssh admin@your-nas-ip mkdir /volume1/shared_data chmod 777 /volume1/shared_data If you prefer a DIY approach, consider repurposing old hardware or using a mini PC to build your own NAS. Tools like TrueNAS Core (formerly FreeNAS) make it easy to create a custom storage solution tailored to your needs. DIY NAS setups offer unparalleled flexibility in terms of hardware selection, redundancy, and cost. 💡 Pro Tip: Use RAID configurations like RAID 1 or RAID 5 for data redundancy. While RAID isn’t a substitute for backups, it provides protection against single-drive failures. Expanding with Virtualization Modern NAS devices often come with virtualization capabilities. For example, Synology NAS can run virtual machines directly, enabling you to host isolated environments for testing, development, or even gaming servers. This feature is a big improvement for anyone looking to maximize their homelab’s utility. Step 3: Networking: The Homelab Backbone Your network infrastructure is the glue that holds your homelab together. Consumer-grade routers might suffice for basic setups, but upgrading to prosumer or enterprise-grade equipment can significantly improve performance and reliability. Routers and Firewalls The UniFi Dream Machine is a top-tier choice for homelab networking. It combines a high-performance router, firewall, and network controller into a single device. Features like intrusion detection and prevention (IDS/IPS) and advanced traffic analytics make it ideal for managing complex network environments. WiFi Coverage For robust wireless coverage, the TP-Link Omada EAP660 HD is an excellent option. Its WiFi 6 capabilities ensure fast and stable connections, even in device-dense environments. Pair it with a managed switch for maximum flexibility. ⚠️ Gotcha: Avoid double NAT setups by ensuring your ISP modem is in bridge mode when using a third-party router. Double NAT can cause connectivity issues and complicate port forwarding. Advanced users might consider segmenting their network using VLANs to isolate devices or services. For example, you could create separate VLANs for IoT devices, personal computers, and your NAS for improved security and organization. Step 4: Compute Power for Advanced Workloads As your homelab evolves, you’ll need more processing power for tasks like virtualization, container orchestration, and development. Mini PCs and small form factor servers are excellent options for scaling your compute resources. Choosing a Mini PC The Intel NUC 12 Pro is a powerhouse in a compact form factor. With support for Intel vPro, it excels at running multiple virtual machines or Kubernetes clusters. For budget-conscious users, the ASUS PN50 Mini PC offers excellent performance for most homelab tasks at a lower price point. Container Orchestration Once you have sufficient compute power, container orchestration tools like Kubernetes or Docker Swarm become invaluable. They allow you to manage multiple containers across your devices efficiently. Here’s an example Kubernetes deployment: # Example Kubernetes deployment for an NGINX service: apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.21 ports: - containerPort: 80 Step 5: Optimizing Storage Performance Fast and reliable storage is essential for a high-performing homelab. For boot drives and high-transaction workloads, SSDs are the way to go. Choosing the Right SSD The Samsung 980 Pro 2TB SSD is a standout choice. Its NVMe interface delivers blazing-fast read/write speeds, making it ideal for databases, Docker images, and operating systems. SSDs ensure quicker boot times and smoother application performance, especially for tasks like video editing or compiling code. Step 6: Security and Remote Access Exposing your homelab to the inter


---
## Developer Hardware Guide 2026: Your Perfect Setup

- URL: https://orthogonal.info/essential-development-hardware-for-2026-complete-setup-guide/
- Date: 2026-01-15
- Category: Tools &amp; Setup
- Summary: The ultimate 2026 developer hardware guide. Find the best laptops, monitors, keyboards, and peripherals to build a productive development environment.

This is the exact hardware stack I use daily. Every recommendation here is something I’ve personally tested across years of development, security work, and running a 30+ container homelab. The right hardware isn’t just about speed—it’s about creating an environment that enhances focus, minimizes frustration, and maximizes productivity. Let’s dive deep into building the ultimate development setup for 2026. 💡 Pro Tip: Investing in high-quality hardware pays dividends in productivity, comfort, and long-term reliability. Think of it as an investment in your career. Blazing Fast Storage: The Backbone of Productivity 📌 TL;DR: Imagine this: you’re debugging a critical issue in your application, and every second counts. You hit “build,” and the progress bar crawls at a snail’s pace. Your frustration mounts as you realize your hardware is holding you back. 🎯 Quick Answer: For developers in 2026, prioritize 32GB+ RAM and NVMe storage over CPU speed. A MacBook Pro M-series handles most workloads; pair it with a 4K monitor, mechanical keyboard, and ergonomic setup. Invest in reliability and ergonomics over raw specs for long-term productivity. Sluggish storage is the silent killer of developer efficiency. Whether you’re compiling code, running virtual machines, or handling large datasets, your storage solution directly impacts performance. Speed isn’t just a luxury—it’s essential. Why NVMe SSDs Are Non-Negotiable Modern NVMe SSDs offer unparalleled speeds compared to older SATA drives. They’re a big improvement for tasks like container builds, database transactions, and managing large projects with thousands of files. Recommended SSDs for Developers: Samsung 980 Pro 2TB NVMe SSD: Industry-leading speed (~$130-180). Western Digital SN850X 2TB NVMe SSD: Budget-friendly without sacrificing performance (~$150-200). Both drives boast read speeds up to 7,000 MB/s. For comparison, traditional SATA SSDs max out at around 550 MB/s. In my own experience, switching from SATA to NVMe reduced Docker build times by nearly 40% for large projects. ⚠️ Gotcha: Ensure your motherboard or laptop supports NVMe drives before purchasing. Older systems may require a BIOS update or additional configuration. Optimizing Storage for Development If you’re juggling multiple development environments—such as running Docker containers alongside virtual machines—storage speed and organization become critical. Here’s how you might partition an NVMe SSD: # Partition NVMe SSD into 3 sections: OS, workspace, and backups sudo parted /dev/nvme0n1 mklabel gpt sudo parted /dev/nvme0n1 mkpart primary ext4 0% 40% sudo parted /dev/nvme0n1 mkpart primary ext4 40% 80% sudo parted /dev/nvme0n1 mkpart primary ext4 80% 100% For developers dealing with sensitive data, consider encrypting your SSD partitions using tools like dm-crypt or BitLocker. 🔐 Security Note: Encrypting your storage is essential if you handle proprietary or sensitive data. Use strong passwords and keep backups in secure locations. Keyboards and Mice: Your Daily Companions You’ll spend countless hours interacting with your keyboard and mouse. These aren’t just peripherals; they’re tools that directly affect your comfort and productivity. A poorly designed keyboard can lead to wrist strain and fatigue, while a sluggish mouse can slow you down. Mechanical Keyboards Mechanical keyboards offer superior tactile feedback, durability, and customization options compared to membrane keyboards. For developers, the ability to type quickly and accurately is critical. Keychron K3 Mechanical Keyboard: Compact, low-profile, and portable (~$75-95). Das Keyboard 4 Professional: Premium build quality and unparalleled typing experience (~$150-200). Consider switches carefully—mechanical keyboards offer a variety of switches, such as Cherry MX Browns for balanced tactile feedback or Cherry MX Reds for smooth keystrokes. If you’re working in shared spaces, opt for quieter switches or O-rings to dampen noise. Precision Mice For a developer, the mouse needs to be precise, ergonomic, and customizable. The Logitech MX Master 3S is my go-to choice for coding and general productivity. Features: Customizable side buttons for IDE shortcuts. Infinite scroll wheel for navigating long code files. Ergonomic design for extended use. If you prefer a simpler mouse, the Razer Basilisk X Hyperspeed offers excellent wireless performance and a sleek profile at a lower price (~$60-80). Displays: The Window to Your Code A high-quality monitor setup makes multitasking seamless. With a larger screen (or dual monitors), you can avoid constant alt-tabbing and keep your IDE, terminal, browser, and documentation open simultaneously. Monitor Recommendations LG 27UP850-W 4K Monitor: Stunning 4K resolution with USB-C support (~$350-400). Dell UltraSharp U2723QE: Factory-calibrated for color accuracy (~$450-500). For maximum productivity, consider a dual-monitor setup. Here’s how to configure it on Linux: # Configure dual monitors using xrandr xrandr --output HDMI-1 --primary --mode 3840x2160 --pos 0x0 --rotate normal xrandr --output DP-1 --mode 1920x1080 --pos 3840x0 --rotate normal 💡 Pro Tip: Use tools like Magnet (macOS) or FancyZones (Windows) to snap windows into preset layouts for better multitasking. Processing Power and Memory: The Engine of Your Setup When choosing a processor and RAM, focus on your workload. Are you running multiple Docker containers, virtual machines, or machine learning models? If so, you’ll need higher specs. Recommended RAM Crucial 64GB DDR4-3200: Ideal for heavy multitasking (~$180-220). Corsair Vengeance LPX 32GB DDR4: Perfect for mid-range setups (~$90-120). For processors, aim for at least an AMD Ryzen 7 or Intel i7. Both offer excellent multi-core performance for compiling large projects or running virtualized environments. Common Pitfalls Buying RAM without checking motherboard compatibility. Underestimating CPU cooling requirements for high-performance setups. Security Hardware: Protecting Your Code As developers, we’re prime targets for cyberattacks. Hardware-based security keys like the YubiKey 5C NFC provide an additional layer of protection against phishing and unauthorized access. How to Set Up YubiKey with GitHub: # Enable YubiKey for GitHub 1. Go to GitHub Settings > Security > Two-factor Authentication. 2. Select 'Security key' and follow the prompts. 3. Insert your YubiKey and tap to verify. ⚠️ Gotcha: Always keep a backup security key in a safe location in case of loss. Homelab Hardware for Self-Hosting 🔧 From my experience: I run Gitea, Immich, Nextcloud, and a full trading system on a TrueNAS box with 64 GB ECC RAM and mirrored NVMe. ECC RAM is non-negotiable for anything running 24/7—I’ve seen bit-flip errors corrupt a ZFS pool on non-ECC memory. Spend the extra $40. Self-hosting tools like GitLab, Jenkins, or databases can give you greater control and privacy. Here are two excellent hardware options: Intel NUC 12 Pro Mini PC: Perfect for heavy workloads (~$450-550). Raspberry Pi 5: Affordable and great for lightweight services (~$75-85). Sample Docker Configuration for Homelabs # Run a local development database using Docker docker run --name dev-postgres -e POSTGRES_PASSWORD=mysecurepassword -d -p 5432:5432 postgres Quick Summary Prioritize fast NVMe storage for immediate productivity gains. Invest in ergonomic keyboards and mice for long-term comfort. Use 4K monitors or dual setups for multitasking efficiency. Upgrade RAM and CPUs based on your workload requirements. Add hardware security keys to safeguard your accounts and codebases. Consider self-hosting development tools for privacy and control. 💡 Pro Tip: Build your setup incrementally during sales events to save money while upgrading effectively. 📚 Related Articles Home Network Segmentation with OPNsense: A Complete Guide Ultimate Guide to Secure Remote Access for Your Homelab How to Make HTTP Requests Through Tor with Python 📬 Get Daily Tech & Market Intelligence Join our free Alpha


---
## Scaling GitOps Securely: Kubernetes Best Practices

- URL: https://orthogonal.info/gitops-security-patterns-kubernetes-scale/
- Date: 2026-01-13
- Category: DevOps
- Summary: Learn best practices for scaling GitOps securely in Kubernetes. Covers RBAC, secrets management, policy enforcement, and multi-cluster security patterns.

Why GitOps Security Matters More Than Ever 📌 TL;DR: Why GitOps Security Matters More Than Ever Imagine this: You’re sipping your coffee on a quiet Monday morning, ready to tackle the week ahead. Suddenly, an alert pops up—your Kubernetes cluster is compromised. 🎯 Quick Answer: Scale GitOps securely by enforcing branch protection and merge approvals on deployment repos, separating cluster credentials per environment, using Progressive Delivery with Argo Rollouts for safe rollouts, and implementing network policies to restrict pod-to-pod traffic as the number of services grows. I manage my production Kubernetes infrastructure using GitOps—every deployment, config change, and secret rotation goes through Git. After catching an unauthorized config change that would have exposed an internal service to the internet, I rebuilt my GitOps pipeline with security as the primary constraint. Here’s how to do it right. Core Principles of Secure GitOps 🔍 From production: I caught a commit in my GitOps repo that changed a service’s NetworkPolicy to allow ingress from 0.0.0.0/0. It was a copy-paste error from a dev environment config. My OPA policy caught it in CI before it ever reached the cluster. Without policy-as-code, that would have been an open door to the internet. 🔧 Why I built this pipeline: I run both trading infrastructure and web services on my cluster. A single misconfiguration could expose trading API keys or allow unauthorized access to financial data. GitOps with signed commits and automated policy checks is the only way I sleep at night. Before jumping into implementation, let’s establish the foundational principles that underpin secure GitOps: Immutability: All configurations must be declarative and version-controlled, ensuring every change is traceable and reversible. Least Privilege Access: Implement strict access controls using Kubernetes Role-Based Access Control (RBAC) and Git repository permissions. No one should have more access than absolutely necessary. Auditability: Maintain a detailed audit trail of every change—who made it, when, and why. Automation: Automate security checks to minimize human error and ensure consistent enforcement of policies. These principles are the backbone of a secure GitOps workflow. Let’s explore how to implement them effectively. Security-First GitOps Patterns for Kubernetes 1. Enabling and Enforcing Signed Commits Signed commits are your first line of defense against unauthorized changes. By verifying the authenticity of commits, you ensure that only trusted contributors can push updates to your repository. Here’s how to configure signed commits: # Step 1: Configure Git to sign commits by default git config --global commit.gpgSign true # Step 2: Verify signed commits in your repository git log --show-signature # Output will indicate whether the commit was signed and by whom To enforce signed commits in GitHub repositories: Navigate to your repository settings. Go to Settings > Branches > Branch Protection Rules. Enable Require signed commits. 💡 Pro Tip: Integrate commit signature verification into your CI/CD pipeline to block unsigned changes automatically. Tools like pre-commit can help enforce this locally. 2. Secrets Management Done Right Storing secrets directly in Git repositories is a disaster waiting to happen. Instead, leverage tools designed for secure secrets management: HashiCorp Vault: Centralized secret storage with dynamic secrets. Kubernetes External Secrets: Sync secrets from external stores to Kubernetes. Here’s an example of creating an encrypted Kubernetes Secret: # Encrypt and create a Kubernetes Secret kubectl create secret generic my-secret \ --from-literal=username=admin \ --from-literal=password=securepass \ --dry-run=client -o yaml | kubectl apply -f - ⚠️ Gotcha: Kubernetes Secrets are base64-encoded by default, not encrypted. Always enable encryption at rest in your cluster configuration. 3. Automated Vulnerability Scanning Integrating vulnerability scanners into your CI/CD pipeline is critical for catching issues before they reach production. Tools like Trivy and Snyk can identify vulnerabilities in container images, dependencies, and configurations. Example using Trivy: # Scan a container image for vulnerabilities trivy image my-app:latest # Output will list vulnerabilities, their severity, and remediation steps 💡 Pro Tip: Schedule regular scans for base images, even if they haven’t changed. New vulnerabilities are discovered daily. 4. Policy Enforcement with Open Policy Agent (OPA) Standardizing security policies across environments is critical for scaling GitOps securely. Tools like OPA and Kyverno allow you to enforce policies as code. For example, here’s a Rego policy to block deployments with privileged containers: package kubernetes.admission deny[msg] { input.request.kind.kind == "Pod" input.request.object.spec.containers[_].securityContext.privileged == true msg := "Privileged containers are not allowed" } Implementing these policies ensures that your Kubernetes clusters adhere to security standards automatically, reducing the likelihood of human error. 5. Immutable Infrastructure and GitOps Security GitOps embraces immutability by design, treating configurations as code that is declarative and version-controlled. This approach minimizes the risk of drift between your desired state and the actual state of your cluster. To further enhance security: Use tools like Flux and Argo CD to enforce the desired state continuously. Enable automated rollbacks for failed deployments to maintain consistency. Use immutable container image tags (e.g., :v1.2.3) to avoid unexpected changes. Combining immutable infrastructure with GitOps workflows ensures that your clusters remain secure and predictable. Monitoring and Incident Response in GitOps Even with the best preventive measures, incidents happen. A proactive monitoring and incident response strategy is your safety net: Real-Time Monitoring: Use Prometheus and Grafana to monitor GitOps workflows and Kubernetes clusters. Alerting: Set up alerts for unauthorized changes, such as direct pushes to protected branches or unexpected Kubernetes resource modifications. Incident Playbooks: Create and test playbooks for rolling back misconfigurations or revoking compromised credentials. ⚠️ Gotcha: Don’t overlook Kubernetes audit logs. They’re invaluable for tracking API requests and identifying unauthorized access attempts. Common Pitfalls and How to Avoid Them Ignoring Base Image Updates: Regularly update your base images to mitigate vulnerabilities. Overlooking RBAC: Audit your RBAC policies to ensure they follow the principle of least privilege. Skipping Code Reviews: Require pull requests and peer reviews for all changes to production repositories. Failing to Rotate Secrets: Periodically rotate secrets to reduce the risk of compromise. Neglecting Backup Strategies: Implement automated backups of critical Git repositories and Kubernetes configurations. My Homelab GitOps Setup I manage 15 services on my homelab through a single Git repo. Everything from media servers to DNS, monitoring stacks, and private web apps — all declared in YAML, versioned in Git, and reconciled by ArgoCD. Here’s how the setup works and why it’s been rock-solid for over a year. The repo follows a clean directory structure that separates concerns: homelab-gitops/ ├── apps/ # Application manifests │ ├── immich/ │ ├── nextcloud/ │ ├── vaultwarden/ │ └── monitoring/ ├── infrastructure/ # Cluster-level resources │ ├── cert-manager/ │ ├── ingress-nginx/ │ └── sealed-secrets/ ├── clusters/ # Cluster-specific overlays │ └── truenas/ │ ├── apps.yaml │ └── infrastructure.yaml └── .sops.yaml # SOPS encryption rules ArgoCD watches this repo and reconciles state automatically. I use an App of Apps pattern so a single root Application deploys everything: apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: homelab-root namespace: argocd spec: project: default source: rep


---
## Mastering Secure Coding: Practical Techniques for Developers

- URL: https://orthogonal.info/secure-coding-made-simple-for-developers/
- Date: 2026-01-09
- Category: Security
- Summary: Master secure coding techniques with practical examples. Covers input validation, authentication, secrets management, and common vulnerability prevention.

Why Developers Must Champion Security 📌 TL;DR: Why Developers Must Champion Security Picture this: It’s a typical Tuesday morning, coffee in hand, when an urgent Slack message pops up. A critical vulnerability has been exposed in your production API, and hackers are already exploiting it. 🎯 Quick Answer: Secure coding starts with three habits: validate and sanitize all inputs at system boundaries, use parameterized queries instead of string concatenation for databases, and apply the principle of least privilege to every service account and API token. These patterns prevent the most common exploitable vulnerabilities. After reviewing 1,000+ pull requests as a security engineer, I can tell you the same 5 insecure coding patterns cause 80% of vulnerabilities. I see them in web apps, APIs, and even in my own algorithmic trading system when I’m coding too fast. Here are the secure coding techniques that actually prevent real vulnerabilities—not textbook theory. Foundational Principles of Secure Coding 🔍 From production: A missing input validation on a query parameter in a REST endpoint allowed an attacker to inject SQL through a search field. The fix was 3 lines of code—a parameterized query. But the incident response took 2 full days: forensics, user notification, credential rotation. Three lines of prevention vs. 48 hours of cleanup. Before jumping into patterns and tools, let’s ground ourselves in the guiding principles of secure coding. Think of these as your compass—they’ll steer you toward safer codebases. 🔧 Why I review for this obsessively: My trading system connects to brokerage APIs with credentials that could execute real trades. A single injection vulnerability or leaked API key isn’t a theoretical risk—it’s direct financial exposure. That’s why I apply the same secure coding standards to personal projects that I enforce in production environments. 1. Least Privilege Grant only the permissions that are absolutely necessary and nothing more. This principle applies to users, systems, and even your code. For example, when connecting to a database, use a dedicated account with minimal permissions: CREATE USER 'app_user'@'%' IDENTIFIED BY 'strong_password'; GRANT SELECT, INSERT ON my_database.* TO 'app_user'@'%'; Never use a root or admin account for application access—it’s akin to leaving your house keys under the doormat. By limiting the scope of permissions, even if credentials are compromised, the potential damage is significantly reduced. 2. Secure Defaults Make the secure option the easiest option. Configure systems to default to HTTPS, enforce strong password policies, and disable outdated protocols like SSLv3 and TLS 1.0. If security requires manual activation, chances are it won’t happen. For example, modern web frameworks like Django and Spring Boot enable secure defaults such as CSRF protection or secure cookies, reducing the burden on developers to configure them manually. When designing software, think about how to make the secure path intuitive. For instance, within your application, ensure that new users are encouraged to create strong passwords by default and that password storage follows best practices like hashing with algorithms such as bcrypt or Argon2. 3. Input Validation and Output Encoding Never trust user input. Validate all data rigorously, ensuring it conforms to expected formats. For example, validating email input: import re def validate_email(email): pattern = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$' if not re.match(pattern, email): raise ValueError("Invalid email format") return email Output encoding is equally essential—it ensures data is safe when rendered in browsers or databases: from html import escape user_input = "<script>alert('XSS')</script>" safe_output = escape(user_input) print(safe_output) # <script>alert('XSS')</script> These measures act as safeguards against attacks like Cross-Site Scripting (XSS) and SQL injection, ensuring that malicious data doesn’t infiltrate your application. 4. Shift-Left Security Security isn’t a final checkpoint—it’s a thread woven throughout development. From design to testing, consider security implications at every stage. By integrating security into the earliest phases of development, issues can be identified and remediated before they become deeply ingrained in the codebase. For example, during the requirements phase, identify potential attack vectors and brainstorm mitigation strategies. During development, use static code analysis tools to catch vulnerabilities as you write code. Finally, during testing, include security tests alongside functional tests to ensure robust coverage. Pro Tip: Integrate security checks into your CI/CD pipeline. Tools like Snyk or GitHub Dependabot can automatically catch vulnerable dependencies early. Secure Coding Patterns for Common Vulnerabilities Let’s translate principles into practice by addressing common vulnerabilities with secure coding patterns. SQL Injection SQL injection occurs when user inputs are concatenated into queries. Here’s an insecure example: # Insecure example query = f"SELECT * FROM users WHERE username = '{user_input}'" cursor.execute(query) This allows malicious users to inject harmful SQL. Instead, use parameterized queries: # Secure example cursor.execute("SELECT * FROM users WHERE username = %s", (user_input,)) Warning: Avoid raw SQL concatenation. Always use parameterized queries or ORM libraries like SQLAlchemy to handle this securely. Cross-Site Scripting (XSS) XSS allows attackers to inject malicious scripts into web pages, exploiting unescaped user inputs. Here’s how to prevent it using Flask: from flask import Flask, escape app = Flask(__name__) @app.route('/greet/<name>') def greet(name): return f"Hello, {escape(name)}!" Using a framework’s built-in protection mechanisms is often the easiest and most reliable way to mitigate XSS vulnerabilities. Error Handling Errors are inevitable, but exposing sensitive information in error messages is a rookie mistake. Here’s the insecure approach: # Insecure example except Exception as e: return f"Error: {e}" # Leaks internal details Instead, log errors securely and return generic messages: # Secure example except Exception as e: logger.error(f"Internal error: {e}") return "An error occurred. Please try again later." Developer-Friendly Security Tools Security doesn’t have to be cumbersome. The right tools can integrate smoothly into your workflow: Static Analysis: Tools like GitHub’s Super-Linter and Bandit scan your code for vulnerabilities. Dynamic Analysis: OWASP ZAP simulates real-world attacks to find weaknesses in your application. Dependency Scanning: Use tools like Snyk to identify libraries with known vulnerabilities. Remember, tooling complements your efforts—it doesn’t replace the need for secure coding practices. By integrating these tools into your CI/CD pipeline, you can automate much of the repetitive work, freeing up time to focus on building features without compromising security. Building a Security-First Culture Security isn’t just technical—it’s cultural. Foster a security-first mindset with these strategies: Collaboration: Break down silos between developers and security teams. Include security experts in early design discussions to identify risks before writing code. Training: Offer regular workshops on secure coding, common vulnerabilities, and emerging threats. Gamify training sessions to make them engaging and memorable. Recognition: Celebrate when developers proactively identify and mitigate vulnerabilities. Publicly acknowledge contributions to security improvements. Pro Tip: Host internal “capture-the-flag” events where developers practice identifying vulnerabilities in simulated environments. This cultural shift ensures that security becomes everyone’s responsibility, rather than an afterthought delegated to specific teams. A security-first culture empowers developers to make informed decisions and take ownership of the security 


---
## Mastering Incident Response Playbooks for Developers

- URL: https://orthogonal.info/incident-response-playbooks-for-developers/
- Date: 2026-01-09
- Category: Security
- Summary: Design effective incident response playbooks for developer teams. Covers triage workflows, communication templates, escalation paths, and post-mortems.

Learn how to design effective and actionable incident response playbooks tailored for developers, ensuring swift and confident handling of security incidents while fostering collaboration with security teams. Why Every Developer Needs Incident Response Playbooks 📌 TL;DR: Learn how to design effective and actionable incident response playbooks tailored for developers, ensuring swift and confident handling of security incidents while fostering collaboration with security teams. 🎯 Quick Answer: Effective incident response playbooks for developers include four phases: detect (automated alerts with clear thresholds), triage (severity classification within 5 minutes), mitigate (predefined rollback procedures), and review (blameless postmortem within 48 hours). Predefined runbooks reduce mean-time-to-recovery by 60% or more. I implemented incident response playbooks after a real production incident where the root cause was trivial—a misconfigured environment variable—but detection took 6 hours because we had no structured process. As a security engineer who also builds trading systems handling real financial data, I can’t afford that kind of response time. Here’s the playbook framework I use now. If this scenario sounds familiar, you’re not alone. Developers are often the first responders to production issues, yet many are unequipped to handle security incidents. This gap can lead to delayed responses, miscommunication, and even exacerbation of the problem. Without a clear plan, it’s easy to get overwhelmed, make mistakes, or waste valuable time chasing red herrings. This is where incident response playbooks come in. A well-crafted playbook serves as a developer’s compass in the chaos, offering step-by-step guidance to mitigate issues quickly and effectively. Playbooks provide a sense of direction amid uncertainty, reducing stress and enabling developers to focus on resolving the issue at hand. By bridging the divide between development and security, playbooks not only enhance incident handling but also Improve your team’s overall security posture. Building Blocks of an Effective Incident Response Playbook 🔍 From production: During a container escape attempt on my Kubernetes cluster, having a pre-written playbook cut response time from an estimated 2+ hours to 23 minutes. The playbook had exact commands for isolating the pod, capturing forensic data, and rotating affected credentials. Without it, I would have been Googling under pressure. An incident response playbook is more than a checklist; it’s a survival guide designed to navigate high-stakes situations. Here are the core elements every robust playbook should include: Roles and Responsibilities: Define who does what. Specify whether developers are responsible for initial triage, escalation, or direct mitigation. For instance, a junior developer might focus on evidence collection, while senior engineers handle mitigation and communication. Step-by-Step Procedures: Break down actions for common scenarios such as DDoS attacks, API abuse, or suspected breaches. Include precise commands, scripts, and examples to ensure clarity, even under pressure. For example, provide a specific command for isolating a compromised container. Communication Protocols: Include templates for notifying stakeholders, escalating to security teams, and keeping customers informed. Clear communication ensures everyone is on the same page and minimizes confusion during incidents. Escalation Paths: Clearly outline when and how to involve higher-level teams, legal counsel, or external partners like incident response firms. For example, if a breach involves customer data, legal and compliance teams should be looped in immediately. Evidence Preservation: Provide guidance on securing logs, snapshots, and other critical data for forensic analysis. Emphasize the importance of preserving evidence before making changes to systems or configurations. Pro Tip: Use diagrams and flowcharts to illustrate complex workflows. Visual aids can be invaluable during high-pressure incidents, helping developers quickly understand the overall process. Example Playbook: Mitigating API Abuse Let’s examine a concrete example of an API abuse playbook. Suppose your API is being abused by a malicious actor, leading to degraded performance and potential outages. Here’s how a playbook might guide developers: # Step 1: Identify the issue # Check for unusual spikes in API traffic or errors kubectl logs deployment/api-service | grep "429" # Step 2: Mitigate the abuse # Temporarily block malicious IPs iptables -A INPUT -s <malicious-ip> -j DROP # Step 3: Add additional logging # Enable debug logs to gather more context kubectl set env deployment/api-service LOG_LEVEL=debug # Step 4: Escalate if necessary # Notify the security team for further investigation curl -X POST -H "Content-Type: application/json" \ -d '{"incident": "API abuse detected", "severity": "high"}' \ https://incident-management.example.com/api/notify # Step 5: Monitor the impact # Ensure the fix is working and monitor for recurrence kubectl logs deployment/api-service This example shows how a step-by-step approach can simplify incident response, ensuring the issue is mitigated while gathering enough data for further analysis. Common Pitfalls and How to Avoid Them Even with a solid playbook, things can go awry. Here are common pitfalls developers face during incident response and how to sidestep them: Overlooking Evidence Preservation: In the rush to fix issues, vital logs or data can be overwritten or lost. Always prioritize securing evidence before making changes. For example, take snapshots of affected systems before restarting or patching them. Ignoring Escalation Protocols: Developers often try to resolve issues solo, delaying critical escalations. Follow the playbook’s escalation paths to avoid bottlenecks. Remember, escalating isn’t a sign of failure—it’s a step toward resolution. Failing to Communicate: Keeping stakeholders in the dark can lead to confusion and mistrust. Use predefined communication templates to ensure consistent updates. For example, send regular Slack updates summarizing the situation, actions taken, and next steps. Overcomplicating Playbooks: Long, jargon-heavy documents are likely to be ignored. Keep playbooks concise, actionable, and written in plain language, ensuring they’re accessible to all team members. Warning: Do not make assumptions about the root cause of an incident. Premature fixes can exacerbate the problem. Investigate thoroughly before taking action. Making Playbooks Developer-Friendly Creating a playbook is only half the battle; ensuring developers use it is the real challenge. Here’s how to make playbooks accessible and developer-friendly: Embed in Tools: Integrate playbooks into platforms developers already use, like GitHub, Slack, or Jira. For example, link playbook steps to automated workflows in your CI/CD pipeline. Use Plain Language: Avoid excessive security jargon. Speak the language of developers to ensure clarity. For instance, instead of saying “perform log aggregation,” say “run this command to consolidate log files.” Include Real-World Examples: Illustrate each section with practical scenarios to make the playbook relatable and actionable. Developers are more likely to engage with examples they’ve encountered in their own work. Train and Practice: Conduct regular tabletop exercises to familiarize developers with the playbook and refine its content based on their feedback. For example, simulate a phishing attack and walk developers through the steps to contain it. Pro Tip: Create a “quick reference” version of the playbook with the most critical steps condensed into one page or slide. This can be a lifesaver during high-stress events. Security and Development Collaboration: The Key to Success 🔧 Why I wrote playbooks for everything: My infrastructure runs trading automation that touches real money and brokerage APIs. A 6-hour incident response isn


---
## Kubernetes Pod Security Standards for Production

- URL: https://orthogonal.info/kubernetes-pod-security-standards-for-production/
- Date: 2026-01-09
- Category: DevOps
- Summary: Implement Kubernetes Pod Security Standards for production workloads. Learn restricted, baseline, and privileged profiles with real-world YAML examples.

A Wake-Up Call: Why Pod Security Standards Are Non-Negotiable 📌 TL;DR: A Wake-Up Call: Why Pod Security Standards Are Non-Negotiable Picture this: you’re on call late at night, troubleshooting a sudden spike in network traffic in your Kubernetes production cluster. 🎯 Quick Answer: Kubernetes Pod Security Standards enforce three profiles—Privileged, Baseline, and Restricted. Production workloads should run under the Restricted profile, which blocks privileged containers, host namespaces, and root users. Apply standards at the namespace level using built-in Pod Security Admission and audit violations before enforcing. I run 30+ containers in production on my own infrastructure, and Kubernetes Pod Security Standards have stopped 3 privilege escalation attempts that I know of. When I first adopted PSS, I thought it was overkill for my cluster size. I was wrong—the restricted profile caught a compromised container image within hours of deployment. Here’s what you need to know. Breaking Down Kubernetes Pod Security Standards 🔍 From production: A third-party container image in my cluster tried to mount the host filesystem via a hostPath volume. The restricted PSS profile blocked it automatically. Without that policy, the container would have had read access to /etc/shadow on the node. I only found out because the audit log flagged the denied request. Kubernetes Pod Security Standards categorize security policies into three modes: Privileged, Baseline, and Restricted. Understanding these modes is crucial for tailoring security to your workloads. Privileged: This mode allows unrestricted access to host resources, including the host filesystem and kernel capabilities. It’s useful for debugging but is a glaring security risk in production. Baseline: The middle ground, suitable for general workloads. It limits risky configurations like privilege escalation but allows reasonable defaults like common volume types. Restricted: The most secure mode, enforcing strict policies such as disallowing privilege escalation, restricting volume types, and preventing unsafe container configurations. This should be the default for sensitive workloads. Warning: Privileged mode is a last resort. Use it only in isolated environments for debugging purposes. For production, aim for Restricted mode wherever feasible. Choosing the right mode depends on the nature of your workloads. For example, a development environment might use Baseline mode to allow flexibility, while a financial application handling sensitive customer data would benefit from Restricted mode to ensure the highest level of security. Step-by-Step Guide to Implementing Pod Security Standards Implementing Pod Security Standards in a production Kubernetes cluster requires careful planning and execution. Here’s a practical roadmap: Step 1: Define Pod Security Policies Start by creating Pod Security Policies (PSP) in YAML format. Below is an example of a Restricted policy: apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: name: restricted spec: privileged: false allowPrivilegeEscalation: false requiredDropCapabilities: - ALL allowedCapabilities: [] volumes: - configMap - emptyDir - secret hostNetwork: false hostIPC: false hostPID: false This policy ensures that pods cannot escalate privileges, access host resources, or use unsafe volume types. Pro Tip: Use tools like Kyverno or OPA Gatekeeper for policy management. They simplify PSP enforcement and provide better auditing capabilities. Step 2: Apply Policies to Namespaces Next, enforce these policies at the namespace level. For example, to apply the Restricted policy to a production namespace: kubectl label namespace production pod-security.kubernetes.io/enforce=restricted This label ensures that pods in the production namespace adhere to the Restricted mode. Warning: Always test policies in a staging environment before applying them to production. Misconfigurations can cause downtime or disrupt workloads. Step 3: Monitor and Audit Compliance Use Kubernetes-native tools to monitor policy violations. For instance, the following command lists pods that fail to comply with enforced policies: kubectl get pods --namespace production --field-selector=status.phase!=Running You can also integrate tools like Gatekeeper or Kyverno to automate compliance checks and generate detailed audit reports. Consider taking compliance monitoring further by integrating alerts into your team’s Slack or email system. For example, you can set up notifications for policy violations using Kubernetes event watchers or third-party tools like Prometheus and Alertmanager. Pro Tip: Schedule periodic audits using Kubernetes Audit Logs to identify gaps in policy enforcement and refine your security posture. Integrating Pod Security Standards into DevSecOps Workflows Scaling security across a dynamic Kubernetes environment requires seamless integration with DevSecOps workflows. Here’s how to make PSS enforcement a part of your CI/CD pipelines: Automating Policy Validation Integrate policy validation steps into your CI/CD pipelines to catch misconfigurations early. Below is an example pipeline step: steps: - name: Validate Pod Security Policies run: | kubectl apply --dry-run=client -f pod-security-policy.yaml This ensures that any new policies are validated before deployment. For more advanced workflows, you can use GitOps tools like Flux or ArgoCD to ensure policies are version-controlled and automatically applied to the cluster. Continuous Auditing Set up automated audits to ensure ongoing compliance. Tools like Kubernetes Audit Logs and OPA Gatekeeper provide visibility into policy violations and enforcement status. Also, integrate these audit reports into centralized dashboards using tools like Grafana. This allows stakeholders to monitor the security posture of the cluster in real-time. Common Pitfalls and Troubleshooting Implementing Pod Security Standards isn’t without challenges. Here are common pitfalls and solutions: Policy Conflicts: Different namespaces may require different policies. Ensure policies are scoped appropriately to avoid conflicts. Downtime Due to Misconfigurations: Test policies thoroughly in staging environments to prevent production disruptions. Lack of Developer Awareness: Educate your team on PSS importance and provide documentation for smooth adoption. Performance Overheads: Security tools may introduce latency. Optimize configurations and monitor resource usage to mitigate performance impacts. Warning: Never attempt to enforce policies globally without understanding workload requirements. Fine-tuned policies are key to balancing security and functionality. Lessons Learned: Real-World Insights 🔧 Why I enforce this: As a security engineer running my own production Kubernetes cluster, I don’t have a dedicated security team watching my pods 24/7. PSS policies are my automated security guard—they enforce rules even when I’m not watching, and they’ve caught things I would have missed in manual reviews. After years of implementing Pod Security Standards, I’ve learned that a gradual, iterative approach works best: Start Small: Begin with non-critical namespaces and scale enforcement gradually. Communicate Clearly: Ensure developers understand policy impacts to minimize resistance. Document Everything: Maintain clear documentation for policies and workflows to ensure consistency. Iterate Continuously: Security needs evolve. Regularly review and update policies to keep pace with threats. Leverage Community Tools: Tools like Kyverno and Gatekeeper have active communities and frequent updates, making them invaluable for staying ahead of security threats. Pro Tip: Use Kubernetes RBAC (Role-Based Access Control) to complement PSS by restricting access to sensitive resources. Quick Summary Kubernetes Pod Security Standards are essential for securing production clusters. Restricted mode should be your default for sensitive workloads. Integrate PSS enforcement into CI/CD pipelines


---
## Algorithmic Trading: A Practical Guide for Engineers

- URL: https://orthogonal.info/algorithmic-trading-basics-for-engineers/
- Date: 2026-01-07
- Category: Finance &amp; Trading
- Summary: A practical guide to algorithmic trading for software engineers. Covers backtesting, strategy design, risk management, and live deployment with Python.

Why Algorithmic Trading is a Major improvement for Engineers 📌 TL;DR: Why Algorithmic Trading is a Major improvement for Engineers Picture this: you’re sipping coffee while your custom trading bot executes hundreds of trades in milliseconds, identifying opportunities and managing risks far better than any human could. 🎯 Quick Answer: Build algorithmic trading systems with a modular pipeline: data ingestion, signal generation, risk management, and execution. Start with paper trading, validate with walk-forward backtesting (not just historical), and always implement position limits and circuit breakers before deploying real capital. I spent the last year building a multi-agent algorithmic trading system using Python and LangGraph. It pulls SEC EDGAR filings, analyzes options flow, and executes strategies autonomously. I’ve made every mistake in this guide—and automated my way past most of them. Here’s what actually works. But it’s not all smooth sailing. I’ve been there—watching a bot I meticulously coded drain my portfolio overnight, all because of a single logic error. While the potential rewards are immense, the risks are equally daunting. The key is a solid foundation, a structured approach, and a clear understanding of the tools and concepts at play. I’ll walk you through the essentials of algorithmic trading, covering everything from core principles to advanced strategies, with plenty of code examples and practical advice along the way. Whether you’re a seasoned engineer or a curious newcomer, you’ll find actionable insights here. Core Principles of Algorithmic Trading 📊 Real example: My first mean-reversion strategy looked incredible in backtesting—12% annual return, low drawdown. In live paper trading, slippage and fill delays cut that to 3%. I had to rebuild the backtester to account for realistic execution costs before the live results matched. 🔧 Why I automated this: I was spending 3+ hours a day on manual analysis—reading SEC filings, checking options chains, computing risk metrics. My LangGraph-based system now does this across 50 tickers in under 2 minutes. The engineering investment paid for itself in the first month. Before you write a single line of code, it’s crucial to grasp the core principles that underpin algorithmic trading. These principles are the building blocks for any successful strategy. Understanding Financial Data At the heart of algorithmic trading lies financial data, usually represented as time series data. This data consists of sequentially ordered data points, such as stock prices or exchange rates, indexed by time. Key components of financial data include: Open, High, Low, Close (OHLC): Standard metrics for candlestick data, representing the day’s opening price, highest price, lowest price, and closing price. Volume: The number of shares or contracts traded during a period. High volume often indicates strong trends. Indicators: Derived metrics like moving averages, Relative Strength Index (RSI), Bollinger Bands, or MACD (Moving Average Convergence Divergence). Financial data can be messy, with missing values or outliers that can distort your algorithms. Engineers need to preprocess and clean this data using statistical methods or libraries like pandas in Python. Risk vs. Reward Every trade involves a balance between risk and reward. Engineers must develop a keen understanding of this dynamic to ensure their strategies are both profitable and sustainable. You’ll frequently encounter metrics like the Sharpe Ratio, which evaluates the risk-adjusted return of a strategy: # Python code to calculate Sharpe Ratio import numpy as np def sharpe_ratio(returns, risk_free_rate=0.01): excess_returns = returns - risk_free_rate return np.mean(excess_returns) / np.std(excess_returns) A higher Sharpe Ratio indicates better performance relative to risk. It’s a cornerstone metric for evaluating strategies. Beyond Sharpe Ratio, engineers also consider metrics like Sortino Ratio (which accounts for downside risk) and Max Drawdown (the maximum loss from peak to trough during a period). Statistical Foundations Algorithmic trading heavily relies on statistical analysis. Here are three key concepts: Mean: The average value of a dataset, useful for identifying trends. Standard Deviation: Measures data variability, crucial for assessing risk. A higher standard deviation means greater volatility. Correlation: Indicates relationships between different assets. For example, if two stocks have a high positive correlation, they tend to move in the same direction. Pro Tip: Use libraries like pandas and NumPy for efficient statistical analysis in Python. Python’s statsmodels library also provides robust statistical tools for regression and hypothesis testing. How to Build an Algorithmic Trading System An algorithmic trading system typically consists of three main components: data acquisition, strategy development, and execution. Let’s explore each in detail. 1. Data Acquisition Reliable data is the foundation of any successful trading strategy. Without accurate data, even the most sophisticated algorithms will fail. Here are common ways to acquire data: APIs: Platforms like Alpha Vantage, Interactive Brokers, and Alpaca offer APIs for real-time and historical data. For cryptocurrency trading, APIs like Binance and Coinbase are popular choices. Web Scraping: Useful for gathering less-structured data, such as news sentiment or social media trends. Tools like BeautifulSoup or Scrapy can help extract this data efficiently. Database Integration: For large-scale operations, consider storing data in a database like PostgreSQL, MongoDB, or even cloud-based solutions like Amazon AWS or Google BigQuery. Warning: Always validate and clean your data. Outliers and missing values can significantly skew your results. 2. Backtesting Backtesting involves evaluating your strategy using historical data. It helps you understand how your algorithm would have performed in the past, which is a good indicator of future performance. Here’s an example of backtesting a simple moving average strategy using the backtrader library: import backtrader as bt class SmaStrategy(bt.Strategy): def __init__(self): self.sma = bt.indicators.SimpleMovingAverage(self.data, period=20) def next(self): if self.data.close[0] < self.sma[0]: self.buy(size=10) # Buy signal elif self.data.close[0] > self.sma[0]: self.sell(size=10) # Sell signal cerebro = bt.Cerebro() data = bt.feeds.YahooFinanceData(dataname='AAPL', fromdate='2022-01-01', todate='2023-01-01') cerebro.adddata(data) cerebro.addstrategy(SmaStrategy) cerebro.run() cerebro.plot() Backtesting isn’t perfect, though. It assumes perfect execution and doesn’t account for slippage or market impact. Engineers can use advanced simulation tools or integrate real-world trading conditions for more accurate results. 3. Execution Execution involves connecting your bot to a broker’s API to place trades. Popular brokers like Interactive Brokers and Alpaca offer robust APIs. Here’s an example of placing a market order using Alpaca’s API: from alpaca_trade_api import REST api = REST('your_api_key', 'your_secret_key', base_url='https://paper-api.alpaca.markets') # Place a buy order api.submit_order( symbol='AAPL', qty=10, side='buy', type='market', time_in_force='gtc' ) Pro Tip: Always use a paper trading account for testing before deploying strategies with real money. Simulated environments allow you to refine your algorithms without financial risk. Advanced Strategies and Common Pitfalls Once you’ve mastered the basics, you can explore more advanced strategies and learn to avoid common pitfalls. Mean Reversion Mean reversion assumes that prices will revert to their average over time. For instance, if a stock’s price is significantly below its historical average, it might be undervalued. Engineers can use statistical tools to identify mean-reverting assets. Momentum Trading Momentum strategies capitalize on continuing trends. If a stock’s price is stea


---
## Advanced Options Strategies for Engineers: A Practical Guide

- URL: https://orthogonal.info/mastering-options-strategies-a-math-driven-approach/
- Date: 2026-01-06
- Category: Finance &amp; Trading
- Summary: Advanced options strategies explained for engineers. Covers spreads, Greeks, volatility trading, hedging techniques, and Python implementation examples.

Options Trading: Where Math Meets Money 📌 TL;DR: Options Trading: Where Math Meets Money Imagine you’re an engineer, accustomed to solving complex systems with elegant solutions. Now picture applying that same mindset to the financial markets. 🎯 Quick Answer: Options are the most engineer-friendly financial instruments because pricing follows quantifiable models like Black-Scholes. Start with covered calls and cash-secured puts, calculate Greeks (delta, theta, vega) programmatically, and use spreads to define maximum risk before entering any position. Options are the most engineer-friendly financial instrument I’ve found. I run iron condors and covered strangles in my own portfolio, and I built automated Greeks calculations into my Python trading system to manage risk in real-time. This guide covers the math and code behind the strategies I actually use. we’ll deep dive into advanced options strategies such as Iron Condors, Spreads, and Butterflies. We’ll bridge the gap between theoretical concepts and practical implementations, using Python to simulate and analyze these strategies. Whether you’re new to options trading or looking to refine your approach, this article will equip you with the tools and insights to succeed. Understanding the Core Concepts of Options Strategies 📊 Real example: I ran an iron condor on SPY during a low-volatility week—sold the 430/425 put spread and 460/465 call spread for $1.82 credit. Volatility stayed compressed, and I kept 78% of the premium at expiration. The key was my automated IV rank calculation flagging the entry point. 🔧 Why I coded this: Manually computing Greeks across a multi-leg options portfolio is error-prone and slow. My system recalculates delta, gamma, theta, and vega exposure every minute during market hours, so I know exactly when to adjust a position before theta decay or a volatility spike hits. Before diving into strategy specifics, it’s essential to grasp the foundational concepts that underpin options trading. These include the mechanics of options contracts, risk-reward profiles, probability distributions, and the all-important Greeks. Let’s break these down to their core components. Options Contracts: The Basics An options contract gives the holder the right, but not the obligation, to buy or sell an underlying asset at a specified price (strike price) before a certain date (expiration). There are two main types of options: Call Options: The right to buy the asset. Traders use calls when they expect the asset price to rise. Put Options: The right to sell the asset. Puts are ideal when traders expect the asset price to fall. Understanding these basic elements is essential for constructing and analyzing strategies. Options are versatile because they allow traders to speculate on price movements, hedge against risks, or generate income from time decay. Pro Tip: Always double-check the expiration date and strike price before executing an options trade. These parameters define your strategy’s success potential and risk exposure. Risk-Reward Profiles Every options strategy is built around a payoff diagram, which visually represents potential profit or loss across a range of stock prices. For example, an Iron Condor has a defined maximum profit and loss, making it ideal for low-volatility markets. Conversely, buying naked options has unlimited profit potential but also poses higher risks. Understanding these profiles allows traders to align strategies with their market outlook and risk tolerance. Probability Distributions and Market Behavior Options pricing models, like Black-Scholes, rely heavily on probability distributions. Engineers can use statistical tools to estimate the likelihood of an asset reaching a specific price, which is crucial for strategy optimization. For instance, the normal distribution is commonly used to model price movements, and traders can calculate probabilities using tools like Python’s SciPy library. Consider this example: If you’re trading an Iron Condor, you’ll focus on the probability of the underlying asset staying within a specific price range. Using historical volatility and implied volatility, you can calculate these probabilities and make data-driven decisions. The Greeks: Sensitivity Metrics The Greeks quantify how an option’s price responds to various market variables. Mastering these metrics is critical for both risk management and strategy optimization: Delta: Measures sensitivity to price changes. A Delta of 0.5 means the option price will move $0.50 for every $1 move in the underlying asset. Delta also reflects the probability of an option expiring in-the-money. Gamma: Tracks how Delta changes as the underlying asset price changes. Higher Gamma indicates more significant shifts in Delta, which is especially important for short-term options. Theta: Represents time decay. Options lose value as they approach expiration, which is advantageous for sellers but detrimental for buyers. Vega: Measures sensitivity to volatility changes. When volatility rises, so does the price of both calls and puts. Rho: Measures sensitivity to interest rate changes. While less impactful in everyday trading, Rho can influence long-dated options. Pro Tip: Use Theta to your advantage by selling options in high-time-decay environments, such as during the final weeks of a contract, but ensure you’re managing the associated risks. Building Options Strategies with Python Let’s move from theory to practice. Python is an excellent tool for simulating and testing options strategies. Beyond simple calculations, Python enables you to model complex, multi-leg strategies and evaluate their performance under different market conditions. Here’s how to start: Simulating Payoff Diagrams One of the first steps in understanding an options strategy is visualizing its payoff diagram. Below is a Python example for creating a payoff diagram for an Iron Condor: import numpy as np import matplotlib.pyplot as plt # Define payoff functions def call_payoff(strike_price, premium, stock_price): return np.maximum(stock_price - strike_price, 0) - premium def put_payoff(strike_price, premium, stock_price): return np.maximum(strike_price - stock_price, 0) - premium # Iron Condor example stock_prices = np.linspace(50, 150, 500) strike_prices = [80, 90, 110, 120] premiums = [2, 1.5, 1.5, 2] # Payoff components long_put = put_payoff(strike_prices[0], premiums[0], stock_prices) short_put = -put_payoff(strike_prices[1], premiums[1], stock_prices) short_call = -call_payoff(strike_prices[2], premiums[2], stock_prices) long_call = call_payoff(strike_prices[3], premiums[3], stock_prices) # Total payoff iron_condor_payoff = long_put + short_put + short_call + long_call # Plot plt.plot(stock_prices, iron_condor_payoff, label="Iron Condor") plt.axhline(0, color='black', linestyle='--') plt.title("Iron Condor Payoff Diagram") plt.xlabel("Stock Price") plt.ylabel("Profit/Loss ($)") plt.legend() plt.show() This code snippet calculates and plots the payoff diagram for an Iron Condor. Adjust the strike prices and premiums to simulate variations of the strategy. The flexibility of Python allows you to customize these simulations for different market conditions. Analyzing Strategy Performance Beyond visualizations, Python can help you analyze the performance of your strategy. For example, you can calculate metrics like maximum profit, maximum loss, and breakeven points. By integrating libraries like NumPy and Pandas, you can process large datasets and backtest strategies against historical market data. Warning: Always consider transaction costs and slippage in your simulations. These factors can significantly impact real-world profitability, especially for high-frequency traders. Advanced Strategies and Real-World Applications Once you’ve mastered the basics, you can explore more advanced strategies and apply them in live markets. Here are some ideas to take your trading to the next level: Dynamic Adjustments Markets are dynamic, and


---
## Zero Trust for Developers: Secure Systems by Design

- URL: https://orthogonal.info/zero-trust-for-developers-a-practical-guide/
- Date: 2026-01-06
- Category: Security
- Summary: Implement Zero Trust architecture as a developer. Covers identity verification, micro-segmentation, least privilege, and practical integration patterns.

Why Zero Trust is Non-Negotiable for Developers 📌 TL;DR: Why Zero Trust is Non-Negotiable for Developers Picture this: It’s a late Friday afternoon, and you’re prepping for the weekend when an alert comes through. An internal service has accessed sensitive customer data without authorization. 🎯 Quick Answer: Zero Trust architecture requires developers to authenticate and authorize every service call, even internal ones. Implement mutual TLS between services, validate every API request with short-lived tokens, and enforce least-privilege access at the code level—never trust the network perimeter alone. I adopted Zero Trust principles after discovering that a misconfigured service account in my Kubernetes cluster had been silently accessing data it shouldn’t have—for weeks. As a security engineer running production infrastructure and trading systems, I learned the hard way that implicit trust is a vulnerability. Here’s how to build Zero Trust into your code from the start. Zero Trust Fundamentals Every Developer Should Know 🔍 From production: A service in my cluster was using a shared service account token to access both a public API and an internal database. When I applied least-privilege Zero Trust policies, I discovered that service had been making database queries it never needed—leftover from a feature that was removed 6 months ago. Removing that access closed an attack surface I didn’t know existed. At its heart, Zero Trust operates on one core principle: “Never trust, always verify.” This means that no user, device, or application is trusted by default—not even those inside the network. Every access request must be authenticated, authorized, and continuously validated. Key Principles of Zero Trust Least Privilege Access: Grant only the minimum permissions necessary for a task. For example, a service responsible for reading data from a database should not have write or delete permissions. Micro-Segmentation: Break down your application into isolated components or zones. This limits the blast radius of potential breaches. Continuous Monitoring: Access and behavior should be continuously monitored. Anomalies—such as a service suddenly requesting access to sensitive data—should trigger alerts or automated actions. Identity-Centric Security: Verify both user and machine identities. Use strong authentication mechanisms like OAuth2, SAML, or OpenID Connect. Warning: Default configurations in many tools and platforms are overly permissive and violate Zero Trust principles. Always review and customize these settings before deployment. Zero Trust in Action: Real-World Example Imagine a microservices-based application where one service handles authentication and another handles user data. Here’s how Zero Trust can be applied: // Example: Token-based authentication in a Node.js API const express = require('express'); const jwt = require('jsonwebtoken'); const app = express(); function authenticateToken(req, res, next) { const token = req.headers['authorization']; if (!token) return res.status(401).json({ message: 'Access denied' }); jwt.verify(token, process.env.JWT_SECRET, (err, user) => { if (err) return res.status(403).json({ message: 'Invalid token' }); req.user = user; next(); }); } app.get('/user-data', authenticateToken, (req, res) => { if (!req.user.permissions.includes('read:user_data')) { return res.status(403).json({ message: 'Insufficient permissions' }); } res.json({ message: 'Secure user data' }); }); In this example, every request to the /user-data endpoint is authenticated and authorized. Tokens are verified against a secret key, and user permissions are checked before granting access. Making Zero Trust Developer-Friendly Let’s be honest: developers are already juggling tight deadlines, feature requests, and bug fixes. Adding security to the mix can feel overwhelming. The key to successful Zero Trust implementation is to integrate it smoothly into your development workflows. Strategies for Developer-Friendly Zero Trust Use Established Tools: Leverage tools like Open Policy Agent (OPA) for policy enforcement and HashiCorp Vault for secrets management. Automate Repetitive Tasks: Automate security checks using CI/CD tools like Snyk, Trivy, or Checkov to scan for vulnerabilities in dependencies and configurations. Provide Clear Guidelines: Ensure your team has access to actionable, easy-to-understand documentation on secure coding practices and Zero Trust principles. Pro Tip: Integrate policy-as-code tools like OPA into your pipelines. This allows you to enforce security policies early in the development cycle. Common Pitfalls to Avoid Overcomplicating Security: Avoid adding unnecessary complexity. Start with the basics—like securing your APIs and authenticating all requests—and iterate from there. Skipping Monitoring: Without real-time monitoring, you’re flying blind. Use tools like Datadog or Splunk to track access patterns and detect anomalies. Ignoring Developer Feedback: If security measures disrupt workflows, developers may find ways to bypass them. Collaborate with your team to ensure solutions are practical and efficient. Practical Steps to Implement Zero Trust 🔧 Why I enforce this everywhere: My infrastructure handles trading automation with brokerage API credentials. Every service that can access those credentials is a potential breach vector. Zero Trust ensures that even if one container is compromised, the blast radius is limited to exactly what that service needs—nothing more. Here’s how you can start applying Zero Trust principles in your projects today: 1. Secure APIs and Microservices Use token-based authentication and enforce strict access controls. For instance, in Python with Flask: # Flask API example with JWT authentication from flask import Flask, request, jsonify import jwt app = Flask(__name__) SECRET_KEY = 'your_secret_key' def authenticate_token(token): try: return jwt.decode(token, SECRET_KEY, algorithms=['HS256']) except jwt.ExpiredSignatureError: return None @app.route('/secure-endpoint', methods=['GET']) def secure_endpoint(): token = request.headers.get('Authorization') if not token: return jsonify({'message': 'Access denied'}), 401 user = authenticate_token(token) if not user or 'read:data' not in user['permissions']: return jsonify({'message': 'Insufficient permissions'}), 403 return jsonify({'message': 'Secure data'}) 2. Enforce Role-Based Access Control (RBAC) Use tools like Kubernetes RBAC or AWS IAM to define roles and permissions. Avoid granting wildcard permissions like s3:* or admin roles to applications or users. 3. Secure Your CI/CD Pipeline Your CI/CD pipeline is a critical part of your development workflow and a prime target for attackers. Ensure it’s secured by: Signing all artifacts to prevent tampering. Scanning dependencies for vulnerabilities using tools like Snyk or Trivy. Restricting access to pipeline secrets and environment variables. Warning: Compromised CI/CD tools can lead to devastating supply chain attacks. Secure them as rigorously as your production systems. 4. Implement Continuous Monitoring Set up centralized logging and monitoring for all services. Tools like ELK Stack, Splunk, or Datadog can help you track access patterns and flag suspicious behavior. Collaboration is Key: Developers and Security Teams Zero Trust is not just a technical framework—it’s a cultural shift. Developers and security teams must work together to make it effective. Shared Responsibility: Security is everyone’s job. Developers should be empowered to make security-conscious decisions during development. Feedback Loops: Regularly review security incidents and update policies based on lessons learned. Continuous Education: Offer training sessions and resources to help developers understand Zero Trust principles and best practices. Pro Tip: Organize regular threat modeling sessions with cross-functional teams. These sessions can uncover hidden vulnerabilities and improve overall security awareness. Quick Summary


---
## Kubernetes Autoscaling: Master HPA and VPA

- URL: https://orthogonal.info/kubernetes-autoscaling-made-easy-master-hpa-and-vpa-for-devops-success/
- Date: 2026-01-06
- Category: DevOps
- Summary: Master Kubernetes Horizontal and Vertical Pod Autoscalers. Learn HPA and VPA configuration, metrics tuning, and scaling strategies for production.

Kubernetes Autoscaling: A Lifesaver for DevOps Teams 📌 TL;DR: Kubernetes Autoscaling: A Lifesaver for DevOps Teams Picture this: it’s Friday night, and you’re ready to unwind after a long week. Suddenly, your phone buzzes with an alert—your Kubernetes cluster is under siege from a traffic spike. 🎯 Quick Answer: Use Kubernetes HPA (Horizontal Pod Autoscaler) to scale pod replicas based on CPU/memory metrics or custom metrics, and VPA (Vertical Pod Autoscaler) to right-size resource requests per pod. HPA handles traffic spikes; VPA optimizes cost. Avoid running both on the same metric simultaneously. Picture this: it’s Friday night, and you’re ready to unwind after a long week. Suddenly, your phone buzzes with an alert—your Kubernetes cluster is under siege from a traffic spike. Pods are stuck in the Pending state, users are experiencing service outages, and your evening plans are in ruins. If you’ve ever been in this situation, you know the pain of misconfigured autoscaling. As a DevOps engineer, I’ve learned the hard way that Kubernetes autoscaling isn’t just a convenience—it’s a necessity. Whether you’re dealing with viral traffic, seasonal fluctuations, or unpredictable workloads, autoscaling ensures your infrastructure can adapt dynamically without breaking the bank or your app’s performance. I’ll share everything you need to know about the Horizontal Pod Autoscaler (HPA) and Vertical Pod Autoscaler (VPA), along with practical tips for configuration, troubleshooting, and optimization. What Is Kubernetes Autoscaling? Kubernetes autoscaling is the process of automatically adjusting resources in your cluster to match demand. This can involve scaling the number of pods (HPA) or resizing the resource allocations of existing pods (VPA). Autoscaling allows you to maintain application performance while optimizing costs, ensuring your system isn’t wasting resources during low-traffic periods or failing under high load. Let’s break down the two main types of Kubernetes autoscaling: Horizontal Pod Autoscaler (HPA): Dynamically adjusts the number of pods in a deployment based on metrics like CPU, memory, or custom application metrics. Vertical Pod Autoscaler (VPA): Resizes resource requests and limits for individual pods, ensuring they have the right amount of CPU and memory to handle their workload efficiently. While these tools are incredibly powerful, they require careful configuration and monitoring to avoid issues. Let’s dive deeper into each mechanism and explore how to use them effectively. Mastering Horizontal Pod Autoscaler (HPA) The Horizontal Pod Autoscaler is a dynamic scaling tool that adjusts the number of pods in a deployment based on observed metrics. If your application experiences sudden traffic spikes—like an e-commerce site during a flash sale—HPA can deploy additional pods to handle the load, and scale down during quieter periods to save costs. How HPA Works HPA operates by continuously monitoring Kubernetes metrics such as CPU and memory usage, or custom metrics exposed via APIs. Based on these metrics, it calculates the desired number of replicas and adjusts your deployment accordingly. Here’s an example of setting up HPA for a deployment: apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: my-app-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: my-app minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50 In this configuration: minReplicas ensures at least two pods are always running. maxReplicas limits the scaling to a maximum of 10 pods. averageUtilization monitors CPU usage, scaling pods up or down to maintain utilization at 50%. Pro Tip: Custom Metrics From experience: CPU-based HPA is a blunt instrument. For web services, I use http_requests_per_second from Prometheus via the prometheus-adapter. For queue workers, scale on queue_depth. The setup: install prometheus-adapter, create a custom-metrics-apiserver config mapping your Prometheus query to a K8s metric, then reference it in your HPA spec. This cut our false scaling events by 70%. Case Study: Scaling an E-commerce Platform Imagine you’re managing an e-commerce platform that sees periodic traffic surges during major sales events. During a Black Friday sale, the traffic could spike 10x compared to normal days. An HPA configured with CPU utilization metrics can automatically scale up the number of pods to handle the surge, ensuring users experience seamless shopping without slowdowns or outages. After the sale, as traffic returns to normal levels, HPA scales down the pods to save costs. This dynamic adjustment is critical for businesses that experience fluctuating demand. Common Challenges and Solutions HPA is a big improvement, but it’s not without its quirks. Here’s how to tackle common issues: Scaling Delay: By default, HPA reacts after a delay to avoid oscillations. If you experience outages during spikes, pre-warmed pods or burstable node pools can help reduce response times. Over-scaling: Misconfigured thresholds can lead to excessive pods, increasing costs unnecessarily. Test your scaling policies thoroughly in staging environments. Limited Metrics: Default metrics like CPU and memory may not capture workload-specific demands. Use custom metrics for more accurate scaling decisions. Cluster Resource Bottlenecks: Scaling pods can sometimes fail if the cluster itself lacks sufficient resources. Ensure your node pools have headroom for scaling. Vertical Pod Autoscaler (VPA): Optimizing Resources If HPA is about quantity, VPA is about quality. Instead of scaling the number of pods, VPA adjusts the requests and limits for CPU and memory on each pod. This ensures your pods aren’t over-provisioned (wasting resources) or under-provisioned (causing performance issues). How VPA Works VPA analyzes historical resource usage and recommends adjustments to pod resource configurations. You can configure VPA in three modes: Off: Provides resource recommendations without applying them. Initial: Applies recommendations only at pod creation. Auto: Continuously adjusts resources and restarts pods as needed. Here’s an example VPA configuration: apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: my-app-vpa spec: targetRef: apiVersion: apps/v1 kind: Deployment name: my-app updatePolicy: updateMode: Auto In Auto mode, VPA will automatically adjust resource requests and limits for pods based on observed usage. Pro Tip: Resource Recommendations From experience: Run VPA in Off mode for at least 2 weeks on production traffic before switching to Auto. Check recommendations with kubectl describe vpa my-app-vpa — look at the “Target” vs your current requests. I’ve seen VPA recommend 3x less memory than what teams had set, saving significant cluster costs. But verify the recommendations match your p99 usage, not just average. Limitations and Workarounds While VPA is powerful, it comes with challenges: Pod Restarts: Resource adjustments require pod restarts, which can disrupt running workloads. Schedule downtime or use rolling updates to minimize impact. Conflict with HPA: Combining VPA and HPA can cause unpredictable behavior. To avoid conflicts, use VPA for memory adjustments and HPA for scaling pod replicas. Learning Curve: VPA requires deep understanding of resource utilization patterns. Use monitoring tools like Grafana to visualize usage trends. Limited Use for Stateless Applications: While VPA excels for stateful applications, its benefits are less pronounced for stateless workloads. Consider the application type before deploying VPA. Advanced Techniques for Kubernetes Autoscaling While HPA and VPA are the bread and butter of Kubernetes autoscaling, combining them with other strategies can unlock even greater efficiency: Cluster Autoscaler: Pair HPA/VPA with Cluster Autoscaler to dynamically add or remove nodes based on pod scheduling requirements. Predictive 


---
## Docker Memory Management: Prevent OOM Errors

- URL: https://orthogonal.info/docker-memory-management-prevent-container-oom-errors-and-optimize-resource-limits/
- Date: 2026-01-06
- Category: DevOps
- Summary: Prevent Docker container OOM kills by configuring memory limits correctly. Learn cgroup settings, monitoring tools, and resource optimization strategies.

It was 2 AM on a Tuesday, and I was staring at a production dashboard that looked like a Christmas tree—red alerts everywhere. The culprit? Yet another Docker container had run out of memory and crashed, taking half the application with it. I tried to stay calm, but let’s be honest, I was one more “OOMKilled” error away from throwing my laptop out the window. Sound familiar? If you’ve ever been blindsided by mysterious out-of-memory errors in your Dockerized applications, you’re not alone. I’ll break down why your containers keep running out of memory, how container memory limits actually work (spoiler: it’s not as straightforward as you think), and what you can do to stop these crashes from ruining your day—or your sleep schedule. Let’s dive in! Understanding How Docker Manages Memory 📌 TL;DR: It was 2 AM on a Tuesday, and I was staring at a production dashboard that looked like a Christmas tree—red alerts everywhere. Yet another Docker container had run out of memory and crashed, taking half the application with it. 🎯 Quick Answer: Prevent Docker OOM kills by setting explicit memory limits with `–memory` and `–memory-swap` flags. A container without limits can consume all host RAM. Set `–memory` to 80% of available resources and monitor with `docker stats` to catch runaway processes before the kernel kills them. Ah, Docker memory management. It’s like that one drawer in your kitchen—you know it’s important, but you’re scared to open it because you’re not sure what’s inside. Don’t worry, I’ve been there. Let’s break it down so you can confidently manage memory for your containers without accidentally causing an OOM (Out of Memory) meltdown in production. First, let’s talk about how Docker allocates memory by default. Spoiler alert: it doesn’t. By default, Docker containers can use as much memory as the host has available. This is because Docker relies on cgroups (control groups), which are like bouncers at a club. They manage and limit the resources (CPU, memory, etc.) that containers can use. If you don’t set any memory limits, cgroups just shrug and let your container party with all the host’s memory. Sounds fun, right? Until your container gets greedy and crashes the whole host. Oops. Now, let’s clear up a common confusion: the difference between host memory and container memory. Think of the host memory as your fridge and the container memory as a Tupperware box inside it. Without limits, your container can keep stuffing itself with everything in the fridge. But if you set a memory limit, you’re essentially saying, “This Tupperware can only hold 2GB of leftovers, no more.” This is crucial because if your container exceeds its limit, it’ll hit an OOM error and get terminated faster than you can say “resource limits.” Speaking of memory limits, let’s talk about why they’re so important in production. Imagine running multiple containers on a single host. If one container hogs all the memory, the others will starve, and your entire application could go down. Setting memory limits ensures that each container gets its fair share of resources, like assigning everyone their own slice of pizza at a party. No fights, no drama. To sum it up: By default, Docker containers can use all available host memory unless you set limits. Use cgroups to enforce memory boundaries and prevent resource hogging. Memory limits are your best friend in production—set them to avoid container OOM errors and keep your app stable. So, next time you’re deploying to production, don’t forget to set those memory limits. Your future self (and your team) will thank you. Trust me, I’ve learned this the hard way—nothing kills a Friday vibe like debugging a container OOM issue. Common Reasons for Out-of-Memory (OOM) Errors in Containers Let’s face it—nothing ruins a good day of deploying to production like an OOM error. One minute your app is humming along, the next it’s like, “Nope, I’m out.” If you’ve been there (and let’s be honest, we all have), it’s probably because of one of these common mistakes. Let’s break them down. 1. Not Setting Memory Limits Imagine hosting a party but forgetting to set a guest limit. Suddenly, your tiny apartment is packed, and someone’s passed out on your couch. That’s what happens when you don’t set memory limits for your containers. Docker allows you to define how much memory a container can use with flags like --memory and --memory-swap. If you skip this step, your app can gobble up all the host’s memory, leaving other containers (and the host itself) gasping for air. 2. Memory Leaks in Your Application Ah, memory leaks—the silent killers of backend apps. A memory leak is like a backpack with a hole in it; you keep stuffing things in, but they never come out. Over time, your app consumes more and more memory, eventually triggering an OOM error. Debugging tools like heapdump for Node.js or jmap for Java can help you find and fix these leaks before they sink your container. However, be cautious when using these tools—heap dumps can contain sensitive data, such as passwords, tokens, or personally identifiable information (PII). Always handle heap dump files securely by encrypting them, restricting access, and ensuring they are not stored in production environments. Mishandling these files could expose your application to security vulnerabilities. 3. Shared Resources Between Containers Containers are like roommates sharing a fridge. If one container (or roommate) hogs all the milk (or memory), the others are going to suffer. When multiple containers share the same host resources, it’s crucial to allocate memory wisely. Use Docker Compose or Kubernetes to define resource quotas and ensure no single container becomes the memory-hogging villain of your deployment. In short, managing memory in containers is all about setting boundaries—like a good therapist would recommend. Set your limits, watch for leaks, and play nice with shared resources. Your containers (and your sanity) will thank you! How to Set Memory Limits for Docker Containers 🔧 From my experience: Don’t guess at memory limits. Run your container under realistic load for 48 hours with no limit set, watch the high-water mark with docker stats, then set your limit at 1.5x that peak. I’ve seen teams set arbitrary 512 MB limits that silently OOM-kill healthy Java services at startup. If you’ve ever had a container crash because it ran out of memory, you know the pain of debugging an Out-Of-Memory (OOM) error. It’s like your container decided to rage-quit because you didn’t give it enough snacks (a.k.a. RAM). But fear not, my friend! Today, I’ll show you how to set memory limits in Docker so your containers behave like responsible adults. Docker gives us two handy flags to manage memory: --memory and --memory-swap. Here’s how they work: --memory: This sets the hard limit on how much RAM your container can use. Think of it as the “you shall not pass” line for memory usage. --memory-swap: This sets the total memory (RAM + swap) available to the container. If you set this to the same value as --memory, swap is disabled. If you set it higher, the container can use swap space when it runs out of RAM. Here’s a simple example of running a container with memory limits: # Run a container with 512MB RAM and 1GB total memory (RAM + swap) docker run --memory="512m" --memory-swap="1g" my-app Now, let’s break this down. By setting --memory to 512MB, we’re saying, “Hey, container, you can only use up to 512MB of RAM.” The --memory-swap flag allows an additional 512MB of swap space, giving the container a total of 1GB of memory to play with. If it tries to use more than that, Docker will step in and say, “Nope, you’re done.” By setting appropriate memory limits, you can prevent resource-hogging containers from taking down your entire server. And remember, just like with pizza, it’s better to allocate a little extra memory than to run out when you need it most. Happy containerizing! Monitoring Container Memory Usage in


---
## Docker Memory Leaks: How to Diagnose and Prevent

- URL: https://orthogonal.info/how-to-fix-docker-memory-leaks-master-cgroups-and-container-memory-management/
- Date: 2026-01-06
- Category: DevOps
- Summary: Diagnose and prevent Docker memory leaks with practical techniques. Covers container profiling, cgroup limits, and memory monitoring best practices.

I’ve debugged OOM kills across 30+ containers on my TrueNAS homelab—everything from Nextcloud to custom trading bots. The diagnostic process below is what I use every time a container starts eating memory it shouldn’t. The Hidden Dangers of Docker Memory Leaks 📌 TL;DR: The Hidden Dangers of Docker Memory Leaks Picture this: It’s the middle of the night, and you’re jolted awake by an urgent alert. Your production system is down, users are complaining, and your monitoring dashboards are lit up like a Christmas tree. 🎯 Quick Answer: Diagnose Docker memory leaks by monitoring `/sys/fs/cgroup/memory/memory.usage_in_bytes` inside containers and comparing against `docker stats`. Use `–memory` limits to surface leaks faster, then inspect the application with language-specific profilers. Cgroup metrics reveal whether the leak is in your app or the runtime. Picture this: It’s the middle of the night, and you’re jolted awake by an urgent alert. Your production system is down, users are complaining, and your monitoring dashboards are lit up like a Christmas tree. After a frantic investigation, the culprit is clear—a containerized application consumed all available memory, crashed, and brought several dependent services down with it. If this scenario sounds terrifyingly familiar, you’ve likely encountered a Docker memory leak. Memory leaks in Docker containers don’t just affect individual applications—they can destabilize entire systems. Containers share host resources, so a single rogue process can spiral into system-wide outages. Yet, many developers and DevOps engineers approach memory leaks reactively, simply restarting containers when they fail. This approach is a patch, not a solution. I’ll show you how to master Docker’s memory management capabilities, particularly through Linux control groups (cgroups). We’ll cover practical strategies to identify, diagnose, and prevent memory leaks, using real-world examples and actionable advice. By the end, you’ll have the tools to bulletproof your containerized infrastructure against memory-related disruptions. What Are Docker Memory Leaks? Understanding Memory Leaks A memory leak occurs when an application allocates memory but fails to release it once it’s no longer needed. Over time, the application’s memory usage grows uncontrollably, leading to significant problems such as: Excessive Memory Consumption: The application uses more memory than anticipated, impacting other processes. Out of Memory (OOM) Errors: The container exceeds its memory limit, triggering the kernel’s OOM killer. System Instability: Resource starvation affects critical applications running on the same host. In containerized environments, the impact of memory leaks is amplified. Containers share the host kernel and resources, so a single misbehaving container can degrade or crash the entire host system. How Leaks Manifest in Containers Let’s say you’ve deployed a Python-based microservice in a Docker container. If the application continuously appends data to a list without clearing it, memory usage will grow indefinitely. Here’s a simplified example: data = [] while True: data.append("leak") # Simulate some processing delay time.sleep(0.1) Run this code in a container, and you’ll quickly see memory usage climb. Left unchecked, it will eventually trigger an OOM error. Symptoms to Watch For Memory leaks can be subtle, but these symptoms often indicate trouble: Gradual Memory Increase: Monitoring tools show a slow, consistent rise in memory usage. Frequent Container Restarts: The OOM killer terminates containers that exceed their memory limits. Host Resource Starvation: Other containers or processes experience slowdowns or crashes. Performance Degradation: Applications become sluggish as memory becomes scarce. Identifying these red flags early is critical to preventing cascading failures. How Docker Manages Memory: The Role of cgroups Docker relies on Linux cgroups (control groups) to manage and isolate resource usage for containers. Cgroups enable fine-grained control over memory, CPU, and other resources, ensuring that each container stays within its allocated limits. Key cgroup Parameters Here are the most important cgroup parameters for memory management: memory.max: Sets the maximum memory a container can use (cgroups v2). memory.current: Displays the container’s current memory usage (cgroups v2). memory.limit_in_bytes: Equivalent to memory.max in cgroups v1. memory.usage_in_bytes: Current memory usage in cgroups v1. These parameters allow you to monitor and enforce memory limits, protecting the host system from runaway containers. Configuring Memory Limits To set memory limits for a container, use the --memory and --memory-swap flags when running docker run. For example: docker run --memory="512m" --memory-swap="1g" my-app In this case: The container is limited to 512 MB of physical memory. The total memory (including swap) is capped at 1 GB. Pro Tip: Always set memory limits for production containers. Without limits, a single container can consume all available host memory. Diagnosing Memory Leaks 🔧 From my experience: Nine times out of ten, the leak isn’t in your application code—it’s a sidecar or logging agent nobody remembers deploying. I once tracked a 2 GB/day leak to a Fluentd container with an unbounded buffer. Always check all containers in the pod, not just the one you wrote. Diagnosing memory leaks requires a systematic approach. Here are the tools and techniques I recommend: 1. Using docker stats The docker stats command provides real-time metrics for container resource usage. Run it to identify containers with steadily increasing memory usage: docker stats Example output: CONTAINER ID NAME MEM USAGE / LIMIT %MEM 123abc456def my-app 1.5GiB / 2GiB 75% If a container’s memory usage rises steadily without leveling off, investigate further. 2. Inspecting cgroup Metrics For deeper insights, check the container’s cgroup memory usage: cat /sys/fs/cgroup/memory/docker/<container_id>/memory.usage_in_bytes This file shows the current memory usage. If usage consistently grows, it’s a strong indicator of a leak. 3. Profiling the Application If the issue lies in your application code, use profiling tools to pinpoint the source of the leak. Examples include: Python: Use tracemalloc to trace memory allocations. Java: Tools like VisualVM or YourKit can analyze heap usage. Node.js: Use Chrome DevTools or clinic.js for memory profiling. 4. Monitoring with Advanced Tools For long-term visibility, integrate monitoring tools like cAdvisor, Prometheus, and Grafana. Here’s how to launch cAdvisor: docker run \ --volume=/var/run/docker.sock:/var/run/docker.sock \ --volume=/sys:/sys \ --volume=/var/lib/docker/:/var/lib/docker/ \ --publish=8080:8080 \ --detach=true \ --name=cadvisor \ google/cadvisor:latest Access the dashboard at http://<host>:8080 to monitor memory usage trends. Warning: Do not rely solely on docker stats for long-term monitoring. Its lack of historical data limits its usefulness for trend analysis. Preventing Memory Leaks Prevention is always better than cure. Here’s how to avoid memory leaks in Docker: 1. Set Memory Limits Always define memory and swap limits for your containers to prevent them from consuming excessive resources. 2. Optimize Application Code Regularly profile your code to address common memory leak patterns, such as: Unbounded collections (e.g., arrays, lists, or maps). Unreleased file handles or network sockets. Lingering event listeners or callbacks. 3. Automate Monitoring and Alerts Use tools like Prometheus and Grafana to set up automated alerts for unusual memory usage patterns. This ensures you’re notified before issues escalate. 4. Use Stable Dependencies Choose stable and memory-efficient libraries for your application. Avoid untested or experimental dependencies that could introduce memory leaks. 5. Test at Scale Simulate production-like loads during testing phases to identify memory behavior under stress. Too


---
## From Layoff to Launch: Start a Startup After Setbacks

- URL: https://orthogonal.info/from-layoff-to-startup-turning-a-difficult-situation-into-a-positive-opportunity/
- Date: 2023-01-20
- Category: Deep Dives
- Summary: Turned a layoff into a startup launch. Practical advice on validating ideas, building MVPs, finding co-founders, and funding your venture after job loss.

I’ve been through industry downturns and watched talented engineers get hit by layoffs that had nothing to do with their ability. Here’s what I’ve learned about turning that setback into the push you needed to build something of your own. A Layoff Can Be Your Startup Catalyst 📌 TL;DR: A Layoff Can Be Your Startup Catalyst Imagine this: You’re sitting at your desk, just another day in the grind of your tech job. Then, an email arrives with the subject line, “Organizational Update.” Your heart sinks. By the time you’ve finished reading, it’s official—you’ve been laid off. 🎯 Quick Answer: A tech layoff can accelerate your startup timeline by eliminating the golden-handcuffs dilemma. Use severance as runway, leverage your professional network for early customers, and validate your idea within 30 days. Over 40% of successful founders started after an involuntary career change. Imagine this: You’re sitting at your desk, just another day in the grind of your tech job. Then, an email arrives with the subject line, “Organizational Update.” Your heart sinks. By the time you’ve finished reading, it’s official—you’ve been laid off. It’s a gut punch, no matter how prepared you think you are. But here’s a secret the most successful entrepreneurs already know: disruption is often the precursor to innovation. Layoffs don’t just close doors; they open windows. Some of the most impactful startups—Slack, Airbnb, WhatsApp—were born out of moments of uncertainty. So, while the initial sting of job loss might feel overwhelming, it’s also a rare opportunity to take control of your future. Let me show you how you can turn this setback into a springboard for your startup dream. Why Layoffs Create Startup Opportunities First, let’s talk about timing. Layoffs can create a unique moment in your career where you suddenly have two precious resources: time and motivation. Without the day-to-day demands of a job, you have the bandwidth to focus on what truly excites you. Combine this with the urgency that comes from needing to redefine your career, and you have a powerful recipe for action. Layoffs also tend to build unexpected networks. When you’re let go alongside other talented professionals, you often find yourself surrounded by people who are equally determined to make something happen. These individuals—engineers, designers, product managers—are looking for purpose, just like you. What better way to channel that collective energy than into building something meaningful? Pro Tip: Use your downtime to identify problems you’re passionate about solving. What’s the one issue you’ve always wanted to tackle but never had the time? This is your chance. Examples of Layoff-Inspired Startups History is filled with examples of successful companies that were born out of layoffs or economic downturns: Slack: Initially developed as an internal communication tool while the founders were pivoting from their failed gaming company. Airbnb: The co-founders launched the platform to cover their rent during the 2008 financial crisis, a time when traditional jobs were scarce. WhatsApp: Brian Acton and Jan Koum, former Yahoo employees, used their severance packages to create a messaging app that solved their frustrations with international communication. What do all these companies have in common? Their founders didn’t let adversity crush them. Instead, they recognized the opportunity in chaos and took action. Could your layoff be the catalyst for your own success story? Assembling Your Startup Dream Team The foundation of any successful startup is its team. If you’ve been laid off, you might already have access to a goldmine of talent. Think of colleagues you’ve worked with, peers in your network, or even acquaintances from tech meetups. These are people whose work you trust and whose skills you respect. But building a great team isn’t just about finding skilled individuals; it’s about creating synergy. Your team should have complementary skills, diverse perspectives, and a shared vision. Here are some practical steps to assemble your dream team: Start with trust: Choose people you’ve worked with and can rely on. A startup’s early days are intense, and you’ll need a team that sticks together under pressure. Define roles early: Ambiguity can lead to chaos. Decide upfront who will handle engineering, product, marketing, and other key functions. Keep it lean: A small, focused team often works more efficiently than a larger, fragmented one. Quality trumps quantity. Look for attitude, not just aptitude: The startup journey is unpredictable, and you need people who are adaptable, resilient, and collaborative. Warning: Be cautious about adding too many co-founders. While it might seem democratic, it can complicate equity splits and decision-making. Networking and Reconnecting Layoffs can sometimes feel isolating, but they’re also an opportunity to reconnect with your professional network. Use LinkedIn or alumni groups to reach out to former colleagues or industry peers. Share your vision and see who resonates with your idea. You might be surprised at how many people are eager for a fresh, exciting challenge. Crafting Your Startup Idea Here’s where things get personal. What’s the problem that keeps you up at night? What’s the product you wish existed but doesn’t? The best startup ideas often come from personal pain points. For example: Slack started as a communication tool for a gaming company. Airbnb solved the founders’ own housing challenges. WhatsApp addressed the need for cheap, reliable international messaging. Think about your own experiences. Have you struggled with inefficient workflows? Lacked access to tools that could make your life easier? Chances are, if you’ve experienced a problem, others have too. Pro Tip: Write down three pain points you’ve encountered in your professional or personal life. Discuss these with your team to identify the most promising one to tackle. Using Market Trends to Guide Your Idea In addition to personal pain points, consider emerging market trends. For example, remote work, AI applications, and sustainability are all sectors experiencing rapid growth. Conduct research to identify gaps in these industries where your skills and interests align. Competitor Analysis Before diving headfirst into your idea, evaluate your competition. What are they doing well? Where are they falling short? Use this analysis to refine your offering and carve out a niche. Tools like SEMrush, Crunchbase, or SimplyAnalytics can provide insights into competitors’ strategies and market positioning. Getting Practical: Build Your MVP Turning an idea into a product is where many aspiring founders stumble. The key is to start small by building a Minimum Viable Product (MVP). An MVP is not a polished, feature-rich product—it’s a prototype designed to test your core idea quickly. Let’s dive into an example. Suppose you want to build a platform for connecting freelance tech talent with startups. Here’s a simple prototype using Python and Flask: # Basic Flask MVP for a talent platform from flask import Flask, jsonify app = Flask(__name__) @app.route('/talents') def get_talents(): return jsonify(["Alice - Frontend Developer", "Bob - Backend Engineer", "Charlie - UX Designer"]) if __name__ == '__main__': app.run(debug=True) This small app lists available talent as JSON data. It’s basic, but it’s a starting point for showcasing your idea. From here, you can expand into a full-fledged application. Common Pitfalls When Prototyping Overthinking: Don’t obsess over scalability or design perfection in your MVP stage. Ignoring feedback: Share your prototype early and often to gather insights from real users. Building without validation: Ensure there’s demand for your solution before investing heavily in development. Validation: Solving Real Problems Once you have your MVP, it’s time to validate your idea. This means asking potential users whether your solution addresses their needs. Here are some methods to help


---
## Securing PHP File Uploads: .htaccess Exploits Fixed

- URL: https://orthogonal.info/using-htaccess-file-to-compromise-loose-ext-control-upload-in-php/
- Date: 2023-01-16
- Category: Security
- Summary: Prevent .htaccess exploits in PHP file uploads. Learn validation techniques, MIME checking, directory permissions, and server-side security best practices.

Why File Upload Security Should Top Your Priority List 📌 TL;DR: Why File Upload Security Should Top Your Priority List Picture this: Your users are happily uploading files to your PHP application—perhaps profile pictures, documents, or other assets. Everything seems to be working perfectly until one day you discover your server has been compromised. 🎯 Quick Answer: PHP file uploads are vulnerable when attackers upload malicious .htaccess files that reconfigure Apache to execute arbitrary code. Fix this by storing uploads outside the web root, validating MIME types server-side, renaming files to random hashes, and disabling .htaccess overrides with `AllowOverride None` in your Apache config. Picture this: Your users are happily uploading files to your PHP application—perhaps profile pictures, documents, or other assets. Everything seems to be working perfectly until one day you discover your server has been compromised. Malicious scripts are running, sensitive data is exposed, and your application is behaving erratically. The root cause? A seemingly innocent .htaccess file uploaded by an attacker to your server. This is not a rare occurrence; it’s a real-world issue that stems from misconfigured .htaccess files and lax file upload restrictions in PHP. we’ll explore how attackers exploit .htaccess files in file uploads, how to harden your application against such attacks, and the best practices that every PHP developer should implement. Understanding .htaccess: A Double-Edged Sword The .htaccess file is a potent configuration tool used by the Apache HTTP server. It allows developers to define directory-level rules, such as custom error pages, redirects, or file handling behavior. For PHP applications, it can even determine which file extensions are treated as executable PHP scripts. Here’s an example of an .htaccess directive that instructs Apache to treat .php5 and .phtml files as PHP scripts: AddType application/x-httpd-php .php .php5 .phtml While this flexibility is incredibly useful, it also opens doors for attackers. If your application allows users to upload files without proper restrictions, an attacker could weaponize .htaccess to bypass security measures or even execute arbitrary code. Pro Tip: If you’re not actively using .htaccess files for specific directory-level configurations, consider disabling their usage entirely via your Apache configuration. Use the AllowOverride None directive to block .htaccess files within certain directories. How Attackers Exploit .htaccess Files in PHP Applications When users are allowed to upload files to your server, you’re essentially granting them permission to place content in your directory structure. Without proper controls in place, this can lead to some dangerous scenarios. Here are the most common types of attacks Using .htaccess: 1. Executing Arbitrary Code An attacker could upload a file named malicious.jpg that contains embedded PHP code. By adding their own .htaccess file with the following line: AddType application/x-httpd-php .jpg Apache will treat all .jpg files in that directory as PHP scripts. The attacker can then execute the malicious code by accessing https://yourdomain.com/uploads/malicious.jpg. Warning: Even if you restrict uploads to specific file types like images, attackers can embed PHP code in those files and use .htaccess to manipulate how the server interprets them. 2. Enabling Directory Indexing If directory indexing is disabled globally on your server (as it should be), attackers can override this by uploading an .htaccess file containing: Options +Indexes This exposes the contents of the upload directory to anyone who knows its URL. Sensitive files stored there could be publicly accessible, posing a significant risk. 3. Overriding Security Rules Even if you’ve configured your server to block PHP execution in upload directories, an attacker can re-enable it by uploading a malicious .htaccess file with the following directive: php_flag engine on This effectively nullifies your security measures and reintroduces the risk of code execution. Best Practices for Securing File Uploads Now that you understand how attackers exploit .htaccess, let’s look at actionable steps to secure your file uploads. 1. Disable PHP Execution The most critical step is to disable PHP execution in your upload directory. Create an .htaccess file in the upload directory with the following content: php_flag engine off Alternatively, if you’re using Nginx, you can achieve the same result by adding this to your server block configuration: location /uploads/ { location ~ \.php$ { deny all; } } Pro Tip: For an extra layer of security, store uploaded files outside of your web root and use a script to serve them dynamically after validation. 2. Restrict Allowed File Types Only allow the upload of file types that your application explicitly requires. For example, if you only need to accept images, ensure that only common image MIME types are permitted: $allowed_types = ['image/jpeg', 'image/png', 'image/gif']; $file_type = mime_content_type($_FILES['uploaded_file']['tmp_name']); if (!in_array($file_type, $allowed_types)) { die('Invalid file type.'); } Also, verify file extensions and ensure they match the MIME type to prevent spoofing. 3. Sanitize File Names To avoid directory traversal attacks and other exploits, sanitize file names before saving them: $filename = basename($_FILES['uploaded_file']['name']); $sanitized_filename = preg_replace('/[^a-zA-Z0-9._-]/', '', $filename); move_uploaded_file($_FILES['uploaded_file']['tmp_name'], '/path/to/uploads/' . $sanitized_filename); 4. Isolate Uploaded Files Consider serving user-uploaded files from a separate domain or subdomain. This isolates the upload directory and minimizes the impact of XSS or other attacks. 5. Monitor Upload Activity Regularly audit your upload directories for suspicious activity. Tools like Tripwire or OSSEC can notify you of unauthorized file changes, including the presence of unexpected .htaccess files. Testing and Troubleshooting Your Configuration Before deploying your application, thoroughly test your upload functionality and security measures. Here’s a checklist: Attempt to upload a PHP file and verify that it cannot be executed. Test file type validation by uploading unsupported formats. Check that directory indexing is disabled. Ensure your .htaccess settings are correctly applied. If you encounter issues, check your server logs for misconfigurations or errors. Common pitfalls include: Incorrect permissions on the upload directory, allowing overwrites. Failure to validate both MIME type and file extension. Overlooking nested .htaccess files in subdirectories. A Real-World Upload Vulnerability I Found During a security audit at a previous job, I found that a file upload endpoint accepted .phtml files. Combined with a misconfigured .htaccess that had AddType application/x-httpd-php .phtml, it was a full remote code execution vulnerability. An attacker could upload a PHP web shell disguised with a .phtml extension and gain complete control of the server. The attack chain, step by step: Attacker discovers the upload endpoint accepts files by extension whitelist, but .phtml was not on the blocklistAttacker uploads shell.phtml containing <?php system($_GET['cmd']); ?>Apache’s existing .htaccess treats .phtml as executable PHPAttacker visits /uploads/shell.phtml?cmd=whoami and gets command executionFrom there: read config files for database credentials, pivot to internal services, exfiltrate data The fix was defense in depth — no single check, but multiple layers that each independently block the attack: <?php /** * Secure file upload handler with defense-in-depth validation. * Each check independently prevents a different attack vector. */ function secureUpload(array $file, string $uploadDir): array { $errors = []; // Layer 1: Validate against a strict ALLOW-list of extensions $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf'];


---
## JavaScript getDay Method: Complete Developer Guide

- URL: https://orthogonal.info/the-hidden-complexities-of-the-getday-method-in-javascript/
- Date: 2022-12-19
- Category: JavaScript
- Summary: Master JavaScript’s getDay() method for date handling. Covers weekday mapping, localization, common pitfalls, and practical examples for web developers.

Why JavaScript’s getDay Method Often Confuses Developers 📌 TL;DR: Why JavaScript’s getDay Method Often Confuses Developers Have you ever experienced frustration when JavaScript’s getDay method returned a number that didn’t match your expectations? Trust me, you’re not alone. 🎯 Quick Answer: JavaScript’s `getDay()` returns 0–6 where 0 is Sunday and 6 is Saturday—not Monday-indexed like ISO 8601. To get Monday as day 1, use `(date.getDay() + 6) % 7`. For locale-aware day names, use `Intl.DateTimeFormat` instead of manual arrays. Have you ever experienced frustration when JavaScript’s getDay method returned a number that didn’t match your expectations? Trust me, you’re not alone. At first glance, this method seems simple: retrieve the day of the week as a number (0 for Sunday through 6 for Saturday). However, hidden complexities such as timezones, zero-based indexing, and daylight saving adjustments frequently lead to mistakes. In my years of programming, I’ve seen developers—myself included—stumble over subtle quirks of getDay. This guide is designed to help you master this method with practical examples, troubleshooting advice, and tips to avoid common pitfalls. Warning: If you’re mixing getDay with timezone-dependent calculations, things can get messy fast. Understanding its behavior in different contexts is critical. Understanding the getDay Method JavaScript’s getDay method is part of the Date object. It returns the day of the week as a number, where: 0 = Sunday 1 = Monday 2 = Tuesday 3 = Wednesday 4 = Thursday 5 = Friday 6 = Saturday The method might seem trivial, but its behavior is tied closely to how JavaScript handles Date objects and timezones. Pro Tip: Don’t confuse getDay with getDate. While getDay returns the weekday, getDate retrieves the numeric day of the month (e.g., 1–31). Simple Example of getDay Let’s start with a straightforward example: const today = new Date(); // Current date const dayOfWeek = today.getDay(); console.log(dayOfWeek); // Outputs a number between 0 and 6 If today is a Wednesday, getDay will return 3. However, things get more interesting when we dive into Date creation and timezones. Creating Accurate Date Objects Before using getDay, you need a reliable Date object. Let’s explore the most common methods for creating dates in JavaScript. Using ISO 8601 Date Strings The ISO format "YYYY-MM-DD" is widely supported and avoids ambiguity: const date = new Date("2023-10-15"); console.log(date.getDay()); // Outputs 0 (Sunday) Note that JavaScript interprets this format as UTC time. If your application relies on local time, this could lead to unexpected outcomes. Using Constructor Arguments For precise control, you can specify each component of the date: const date = new Date(2023, 9, 15); // October 15, 2023 console.log(date.getDay()); // Outputs 0 (Sunday) Remember, months are zero-indexed (January = 0, February = 1, etc.). Forgetting this detail can lead to off-by-one errors. Common Pitfalls in Date Creation One common mistake is using unsupported or ambiguous formats: const invalidDate = new Date("15-10-2023"); console.log(invalidDate); // Outputs "Invalid Date" Always stick to ISO 8601 or proper constructor arguments to avoid parsing errors. Warning: Avoid date formats like "MM/DD/YYYY". These rely on locale settings and can lead to inconsistent behavior. How Timezones Impact getDay Timezones are a notorious source of confusion when working with Date objects. JavaScript’s Date is internally based on UTC but reflects the local timezone of the browser. This discrepancy can affect getDay calculations. Timezone Example Consider the following example: const utcDate = new Date("2023-10-15T00:00:00Z"); // UTC midnight console.log(utcDate.getDay()); // Outputs 0 (Sunday) const localDate = new Date("2023-10-15"); console.log(localDate.getDay()); // Output depends on your local timezone In New York (UTC-4), the local date might still fall on Saturday due to timezone shifts. Pro Tip: Use toUTCString and toLocaleString to compare UTC and local time interpretations. Handling Daylight Saving Time Daylight Saving Time (DST) is another wrinkle. During transitions into or out of DST, local time shifts by an hour, potentially altering the day. Libraries like date-fns or luxon are invaluable for handling these scenarios. Enhancing Accuracy with Libraries When precision is critical, third-party libraries can simplify your work. Here’s an example using date-fns-tz: import { utcToZonedTime } from 'date-fns-tz'; function getWeekDayInTimezone(dateString, timezone) { const utcDate = new Date(dateString); const zonedDate = utcToZonedTime(utcDate, timezone); const weekDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; return weekDays[zonedDate.getDay()]; } const weekday = getWeekDayInTimezone("2023-10-15T00:00:00Z", "America/New_York"); console.log(weekday); // Outputs: Saturday Debugging Unexpected Results Even with careful implementation, issues can arise. Here’s how to troubleshoot: Validate Input Format Ensure your date strings use the “YYYY-MM-DD” format. Ambiguous or invalid formats lead to errors. Inspect Local vs UTC Time Log intermediate values to verify how the Date object is interpreted: const date = new Date("2023-10-15"); console.log(date.toString()); // Local time interpretation console.log(date.toUTCString()); // UTC time interpretation Warning: Always account for timezone differences when working with users across multiple regions. Real-World Use Cases Task Scheduling: Determine the day of the week for recurring events. Dynamic Content: Show specific content based on the day (e.g., “Monday Promotions”). Date Validation: Ensure business-critical dates fall within valid weekdays. Analytics: Group data by day of the week for trends analysis. Date Handling Pitfalls I’ve Hit in Production I once shipped a scheduling feature that showed Monday meetings on Sunday for users in UTC-negative timezones. The bug was subtle: I was using getDay() on a date string parsed as UTC, but displaying it in the user’s local timezone. For a user in Pacific Time (UTC-8), a meeting at Monday 2:00 AM UTC rendered as Sunday 6:00 PM local time—and getDay() faithfully returned 0 (Sunday) instead of 1 (Monday). It took three bug reports before I reproduced it. The timezone trap: getDay() returns the day based on the browser’s local time, not UTC. If your date was created from a UTC string, the local interpretation might be a different day entirely. This is especially dangerous near midnight boundaries. getUTCDay() vs getDay(): Use getUTCDay() when your application logic is based on a fixed reference timezone (like UTC). Use getDay() when you genuinely want the day as the user perceives it locally. In my apps, the rule is simple: if the date came from an API (ISO 8601 format), use getUTCDay(). If it came from user input in a date picker, use getDay(). Here’s the timezone-safe day-of-week function I now use everywhere: /** * Returns the day of week (0-6) for a given date in a specific timezone. * Avoids the getDay() local-time trap entirely. */ function getDayInTimezone(date, timezone = 'UTC') { const formatter = new Intl.DateTimeFormat('en-US', { timeZone: timezone, weekday: 'short' }); const dayMap = { 'Sun': 0, 'Mon': 1, 'Tue': 2, 'Wed': 3, 'Thu': 4, 'Fri': 5, 'Sat': 6 }; const dayStr = formatter.format(date); return dayMap[dayStr]; } // Examples: const meeting = new Date('2024-03-11T02:00:00Z'); // Monday 2AM UTC console.log(getDayInTimezone(meeting, 'UTC')); // 1 (Monday) console.log(getDayInTimezone(meeting, 'America/Los_Angeles')); // 0 (Sunday) console.log(getDayInTimezone(meeting, 'Asia/Tokyo')); // 1 (Monday) Testing across timezones: You can’t just test in your own timezone and call it done. I set up a simple test harness that overrides the timezone environment variable and runs assertions against known date/day pairs. In CI, I test with at least three timezones: UTC, a positive offset (like Asia/Tokyo, UTC+9), an


---
## Advanced CSS Optimization for Peak Web Performance

- URL: https://orthogonal.info/maximizing-performance-expert-tips-for-optimizing-your-css/
- Date: 2022-12-11
- Category: Deep Dives
- Summary: Boost website speed with advanced CSS optimization. Covers critical CSS extraction, unused style removal, selector performance, and rendering optimization.

Imagine launching a visually stunning website, carefully crafted to dazzle visitors and convey your message. But instead of rave reviews, the feedback you get is less than flattering: “It’s slow,” “It feels unresponsive,” “Why does it take so long to load?” Sound familiar? The culprit might be hidden in plain sight—your CSS. CSS, while essential for modern web design, can become a silent performance bottleneck. A bloated or poorly optimized stylesheet can slow down rendering, frustrate users, and even impact your website’s SEO and conversion rates. Fortunately, optimizing your CSS doesn’t require a complete overhaul. With smart strategies and an understanding of how browsers process CSS, you can turn your stylesheets into performance powerhouses. Let me guide you through advanced techniques that will transform your approach to CSS optimization. From Using modern features to avoiding common pitfalls, this is your complete roadmap to faster, smoother, and more maintainable websites. Why CSS Optimization Matters 📌 TL;DR: Imagine launching a visually stunning website, carefully crafted to dazzle visitors and convey your message. 🎯 Quick Answer: Optimize CSS performance by inlining critical above-the-fold styles, deferring non-critical stylesheets with `media=”print”` swap, removing unused selectors with PurgeCSS, and minimizing specificity depth. These techniques can reduce render-blocking CSS by 60–80% and improve Largest Contentful Paint scores significantly. I’ve optimized the CSS on production sites where every millisecond of render time affected user engagement metrics. Most CSS optimization advice is outdated — here’s what actually moves the needle with modern browsers. Before diving into the technical details, let’s understand why CSS optimization is critical. Today’s users expect websites to load within seconds, and performance directly impacts user experience, search engine rankings, and even revenue. According to Google, 53% of mobile users abandon a website if it takes longer than 3 seconds to load. Bloated CSS can contribute to longer load times, particularly on mobile devices with limited bandwidth. Also, poorly organized stylesheets make maintaining and scaling a website cumbersome. Developers often face challenges such as conflicting styles, high specificity, and duplicated code. By optimizing your CSS, you not only improve performance but also create a more sustainable and collaborative codebase. Leverage Modern CSS Features Staying current with CSS standards is more than a luxury; it’s a necessity. Modern features like CSS Grid, Flexbox, and Custom Properties (CSS variables) not only simplify your code but also improve performance by reducing complexity. /* Example: Using CSS Grid for layout */ .container { display: grid; grid-template-columns: repeat(3, 1fr); /* Three equal-width columns */ gap: 16px; /* Space between grid items */ } /* Example: CSS Custom Properties */ :root { --primary-color: #007bff; --secondary-color: #6c757d; } .button { background-color: var(--primary-color); color: #fff; } Features like CSS Grid eliminate the need for outdated techniques such as float or inline-block, which often result in layout quirks and additional debugging overhead. By using modern properties, you allow browsers to optimize rendering processes for better performance. Pro Tip: Use tools like Can I Use to verify browser support for modern CSS features. Always include fallbacks for older browsers if necessary. Structure Your CSS with a Style Guide Consistency is key to maintainable and high-performing CSS. A style guide ensures your code adheres to a predictable structure, making it easier to optimize and debug. /* Good CSS: Clear and structured */ .button { background-color: #28a745; color: #fff; padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; } /* Bad CSS: Hard to read and maintain */ .button {background:#28a745;color:white;padding:10px 15px;border:none;border-radius:5px;cursor:pointer;} Tools like Stylelint can enforce adherence to a style guide, helping you catch errors and inconsistencies before they affect performance. Warning: Avoid overly specific selectors like div.container .header .button. They increase specificity and make your stylesheets harder to maintain, often leading to performance issues. Reduce CSS File Size Large CSS files can slow down page loads, especially on mobile devices or slower networks. Start by auditing your stylesheet for unused or redundant selectors and declarations. Tools like PurgeCSS or UnCSS can automate this process. Minification is another critical optimization step. By removing whitespace, comments, and unnecessary characters, you reduce file size without altering functionality. /* Original CSS */ .button { background-color: #007bff; color: #fff; padding: 10px 20px; } /* Minified CSS */ .button{background-color:#007bff;color:#fff;padding:10px 20px;} Also, consider using CSS preprocessors like Sass or Less to modularize your code and generate optimized output. Optimize Media Queries Media queries are indispensable for responsive design, but they can easily become bloated and inefficient. Group related styles together and avoid duplicating declarations across multiple queries. /* Before: Duplicated media queries */ @media (max-width: 768px) { .button { font-size: 14px; } } @media (max-width: 576px) { .button { font-size: 12px; } } /* After: Consolidated queries */ .button { font-size: 16px; } @media (max-width: 768px) { .button { font-size: 14px; } } @media (max-width: 576px) { .button { font-size: 12px; } } Organizing your media queries reduces redundancy and improves maintainability. Optimize Font Loading Web fonts can significantly impact loading times, especially if they block rendering. The font-display property gives you control over how fonts load, improving user experience. @font-face { font-family: 'CustomFont'; src: url('customfont.woff2') format('woff2'); font-display: swap; /* Allows fallback font display */ } Using font-display: swap prevents the dreaded “flash of invisible text” (FOIT) by displaying fallback fonts until the custom font is ready. Use GPU-Friendly Properties Properties like transform and opacity are processed by the GPU, making them faster than CPU-bound properties like top and left. This is particularly important for animations and transitions. /* Before: Using top/left */ .element { position: absolute; top: 50px; left: 100px; } /* After: Using transform */ .element { transform: translate(100px, 50px); } By offloading work to the GPU, you achieve smoother animations and faster rendering. Warning: Avoid overusing GPU-friendly properties like will-change. Overuse can lead to memory issues and degraded performance. Optimize Visual Effects When creating shadows, clipping effects, or other visuals, choose properties optimized for performance. For example, box-shadow and clip-path are more efficient than alternatives like mask. 💡 In practice: On a site I optimized, extracting critical above-the-fold CSS and inlining it in the <head> cut First Contentful Paint by 800ms. The tool I used was critical (npm package) — it loads your page in a headless browser, identifies what’s visible in the viewport, and extracts just those styles. Everything else loads async. /* Example: Efficient shadow */ .card { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } /* Example: Efficient clipping */ .image { clip-path: circle(50%); } These properties are designed for modern browsers, ensuring smoother rendering and less computational overhead. Quick Summary Stay updated on modern CSS features like Grid, Flexbox, and Custom Properties to simplify code and improve performance. Adopt a consistent style guide to make your CSS manageable and efficient. Minimize file size through audits, purging unused styles, and minification. Simplify media queries to avoid redundancy and enhance responsiveness. Optimize font loading with properties like font-display: swap. Leverage GPU-friendly propertie


---
## Python Optimization: Proven Tips for Performance

- URL: https://orthogonal.info/maximizing-performance-expert-tips-for-optimizing-your-python/
- Date: 2022-12-11
- Category: Python
- Summary: Speed up Python code with proven optimization techniques. Covers profiling, algorithmic improvements, caching, NumPy vectorization, and memory management.

Python is widely celebrated for its simplicity, readability, and versatility. It powers everything from web applications to machine learning models, making it a go-to language for developers worldwide. However, Python’s ease of use often comes with a tradeoff: performance. As an interpreted language, Python can be slower than compiled languages like C++ or Java, and this can lead to bottlenecks in performance-critical applications. Understanding when and how to optimize your Python code can mean the difference between an application that runs smoothly and one that suffers from inefficiencies, slowdowns, or even outright failures. But optimization is not always necessary. As the saying goes, “premature optimization is the root of all evil.” It’s important to identify areas where optimization matters most—after all, spending time improving code that doesn’t significantly impact performance is often a wasted effort. This guide will help you strike the right balance, showing you how to identify performance bottlenecks and apply targeted optimizations to make your Python applications faster and more efficient. Whether you’re a beginner or an experienced developer, this complete article will equip you with the tools and techniques needed to optimize Python code effectively. Table of Contents 📌 TL;DR: Python is widely celebrated for its simplicity, readability, and versatility. It powers everything from web applications to machine learning models, making it a go-to language for developers worldwide. However, Python’s ease of use often comes with a tradeoff: performance. 🎯 Quick Answer: Optimize Python performance by using list comprehensions (2–3× faster than loops), leveraging built-in functions like `map()` and `filter()`, caching with `functools.lru_cache`, choosing proper data structures (sets for lookups, deques for queues), and profiling with `cProfile` before optimizing. For CPU-bound work, use multiprocessing or C extensions. After optimizing Python data pipelines processing millions of SEC filings, here are the techniques that actually matter. Python’s flexibility is its strength, but that flexibility has a performance cost — knowing where to spend your optimization budget makes all the difference. 1. Profiling Your Python Code 2. Data Structure Optimization 3. Algorithm Complexity & Big-O 4. NumPy & Vectorization 5. Caching & Memoization 6. Generators & Lazy Evaluation 7. String Optimization 8. Concurrency: Threading vs Multiprocessing vs Asyncio 9. Database Query Optimization 10. Real-World Case Study 11. Common Pitfalls 12. Conclusion 1. Profiling Your Python Code When optimizing Python code, the first step is understanding which parts of your program are consuming the most time and resources. Profiling tools help identify performance bottlenecks, allowing you to focus on improving the most critical areas. This section introduces four essential profiling tools: cProfile, line_profiler, memory_profiler, and timeit. Each tool has a specific purpose, from tracking execution time to analyzing memory usage. cProfile: Profiling Entire Programs Python’s built-in cProfile module provides a detailed overview of your code’s performance. It tracks the time spent in each function and outputs a report that highlights the most time-consuming functions. import cProfile import pstats def example_function(): total = 0 for i in range(1, 10000): total += i ** 2 return total if __name__ == "__main__": profiler = cProfile.Profile() profiler.enable() example_function() profiler.disable() stats = pstats.Stats(profiler) stats.sort_stats('time').print_stats(10) The above script will output the top 10 functions sorted by execution time. This helps you pinpoint which functions are slowing your program. line_profiler: Profiling Line-by-Line Execution The line_profiler tool is useful for profiling specific functions at a line-by-line level. You can use the @profile decorator to annotate the functions you want to analyze. Note that you need to install line_profiler using pip install line-profiler. from time import sleep @profile def slow_function(): total = 0 for i in range(5): total += i sleep(0.5) # Simulate a slow operation return total if __name__ == "__main__": slow_function() Run the script with kernprof -l -v your_script.py. The output shows execution time for each line in the annotated function, helping you identify inefficiencies. memory_profiler: Tracking Memory Usage To analyze memory usage, use memory_profiler. Install it with pip install memory-profiler and annotate functions with @profile to track memory consumption line by line. @profile def memory_intensive_function(): data = [i ** 2 for i in range(100000)] return sum(data) if __name__ == "__main__": memory_intensive_function() Run your script with python -m memory_profiler your_script.py. The output shows memory usage before and after each line, helping you optimize memory-hungry operations. timeit: Micro-Benchmarking For quick, isolated benchmarks, use the timeit module. This tool is ideal for measuring the execution time of small pieces of code. import timeit statement = "sum([i ** 2 for i in range(1000)])" execution_time = timeit.timeit(statement, number=1000) print(f"Execution time: {execution_time:.4f} seconds") The above code measures how long it takes to execute the statement 1000 times. Use timeit to compare different implementations of the same functionality. Conclusion Each of these profiling tools addresses a unique aspect of performance analysis. Use cProfile for a high-level overview, line_profiler for detailed line-by-line timing, memory_profiler for memory usage, and timeit for quick micro-benchmarks. Together, these tools enable you to diagnose and optimize your Python code effectively. 2. Data Structure Optimization List vs deque for Queue Operations When implementing queues, choosing the right data structure is crucial. While Python’s list is versatile, it is inefficient for queue operations due to O(n) complexity for popping from the front. The collections.deque, on the other hand, provides O(1) time complexity for appending and removing from both ends. from collections import deque from timeit import timeit # List as a queue list_queue = [i for i in range(10_000)] list_time = timeit("list_queue.pop(0)", globals=globals(), number=1000) # Deque as a queue deque_queue = deque(range(10_000)) deque_time = timeit("deque_queue.popleft()", globals=globals(), number=1000) print(f"List pop(0): {list_time:.6f}s") print(f"Deque popleft(): {deque_time:.6f}s") Benchmark: On average, deque.popleft() is several times faster than list.pop(0), making it the better choice for queues. Set vs List for Membership Testing Testing for membership in a set is O(1), while in a list, it is O(n). This makes set more efficient for frequent membership checks. # Membership testing large_list = [i for i in range(1_000_000)] large_set = set(large_list) list_time = timeit("999_999 in large_list", globals=globals(), number=1000) set_time = timeit("999_999 in large_set", globals=globals(), number=1000) print(f"List membership test: {list_time:.6f}s") print(f"Set membership test: {set_time:.6f}s") Benchmark: Membership testing in a set is significantly faster, especially for large datasets. Dict Comprehensions vs Loops Using a dictionary comprehension is more concise and often faster than a traditional loop for creating dictionaries. # Dictionary comprehension comprehension_time = timeit("{i: i ** 2 for i in range(1_000)}", number=1000) # Traditional loop def create_dict(): d = {} for i in range(1_000): d[i] = i ** 2 return d loop_time = timeit("create_dict()", globals=globals(), number=1000) print(f"Dict comprehension: {comprehension_time:.6f}s") print(f"Dict loop: {loop_time:.6f}s") Benchmark: Comprehensions are generally faster and should be preferred when possible. collections.Counter, defaultdict, and namedtuple The collections module provides powerful alternatives to standard Python structures: Counter: Ideal for coun


---
## JavaScript Optimization: Tips to Boost Performance

- URL: https://orthogonal.info/maximizing-performance-expert-tips-for-optimizing-your-javascripts/
- Date: 2022-12-11
- Category: JavaScript
- Summary: Optimize JavaScript performance with practical tips. Covers DOM manipulation, event delegation, lazy loading, Web Workers, and memory leak prevention.

Imagine this scenario: you’re troubleshooting a painfully slow web application late at night, and every page load feels like an eternity. You’ve already optimized images, reduced CSS bloat, and upgraded server hardware, yet the app remains sluggish. The likely culprit? Inefficient JavaScript. If you’ve been there, you’re not alone. JavaScript is the lifeblood of modern web development, but when poorly optimized, it becomes a performance bottleneck. I’ll walk you through actionable strategies to optimize your JavaScript for speed, maintainability, and scalability. Whether you’re a seasoned developer or just starting out, these tips and techniques will Improve your coding game. 1. Embrace Modern JavaScript Features 📌 TL;DR: Imagine this scenario: you’re troubleshooting a painfully slow web application late at night, and every page load feels like an eternity. You’ve already optimized images, reduced CSS bloat, and upgraded server hardware, yet the app remains sluggish. Inefficient JavaScript. 🎯 Quick Answer: Boost JavaScript performance by debouncing event handlers, replacing `forEach` with `for` loops in hot paths (up to 3× faster), using `requestAnimationFrame` for DOM updates, lazy-loading modules with dynamic `import()`, and avoiding layout thrashing by batching DOM reads and writes separately. I build browser-based privacy tools and financial calculators that need to run fast on any device. Here’s what I’ve learned about JavaScript performance — not theory, but the optimizations that actually showed up in Lighthouse and real user metrics. JavaScript evolves continually, with each ECMAScript version adding new syntax improvements, performance enhancements, and features. Using modern JavaScript ensures cleaner, faster, and more maintainable code while benefiting from optimizations in modern JavaScript engines like V8, SpiderMonkey, and Chakra. // ES5: Verbose and less readable var numbers = [1, 2, 3]; var doubled = numbers.map(function(num) { return num * 2; }); // ES6+: Concise and optimized const numbers = [1, 2, 3]; const doubled = numbers.map(num => num * 2); Modern JavaScript constructs are not only easier to write and read but are also fully optimized in modern browsers. Features such as destructuring, default parameters, and template literals allow developers to write less boilerplate code while improving clarity. // Destructuring allows easy variable assignment const user = { name: 'Alice', age: 30 }; const { name, age } = user; console.log(name); // Alice // Default parameters simplify function calls function greet(name = 'Guest') { console.log(`Hello, ${name}!`); } greet(); // Hello, Guest! // Template literals make string handling easier const item = 'laptop'; const price = 999; console.log(`The ${item} costs $${price}.`); Pro Tip: Use tools like Babel or esbuild to transpile your code for older browsers while working with the latest syntax during development. 2. Avoid var: Use let and const The var keyword has long been associated with scoping issues due to its function-level scope and hoisting behavior. To write safer and more predictable code, opt for let and const, which are block-scoped. This approach also eliminates common bugs caused by variable hoisting, ensuring variables are only accessible where they are intended to be. // Using var (poor practice) function demo() { if (true) { var x = 5; } console.log(x); // Accessible outside block: 5 } // Using let (better practice) function demo() { if (true) { let x = 5; } console.log(x); // ReferenceError: x is not defined } // Using const for immutability const PI = 3.14; console.log(PI); // 3.14 Using const wherever possible is not just about immutability but also about communicating intent. If a value should not change, declaring it with const helps both developers and tools like linters understand the code better. Warning: Overusing let instead of const can lead to accidental reassignment. Use const whenever possible to signal intention clearly. 3. Optimize Asynchronous Code with async and await Managing asynchronous operations is crucial for non-blocking JavaScript. While callbacks and promises have traditionally been used, they can quickly lead to nested and hard-to-read “callback hell.” The async and await syntax offers a cleaner, more intuitive way to handle asynchronous tasks. // Callback hell example fetchData(function(data) { processData(data, function(result) { saveResult(result, function(response) { console.log(response); }); }); }); // Async/await example async function handleData() { try { const data = await fetchData(); const result = await processData(data); const response = await saveResult(result); console.log(response); } catch (error) { console.error('Error:', error); } } Using async and await not only makes the code more readable but also simplifies error handling. Unlike nested callbacks, which can easily obscure error sources, try/catch blocks in async functions provide clear and centralized error management. Pro Tip: Always wrap async/await operations in try/catch blocks to handle errors gracefully. For multiple asynchronous operations, consider using Promise.all to run them in parallel. 4. Leverage Functional Array Methods Imperative loops like for and forEach are fine for simple tasks but can make code harder to maintain when handling complex transformations. Functional methods like map, filter, and reduce are more expressive and concise. // Imperative approach const numbers = [1, 2, 3, 4]; const evens = []; for (let i = 0; i < numbers.length; i++) { if (numbers[i] % 2 === 0) { evens.push(numbers[i]); } } // Declarative approach const numbers = [1, 2, 3, 4]; const evens = numbers.filter(num => num % 2 === 0); Functional array methods allow you to chain operations, making complex workflows easier to understand and debug. For example, you can filter, map, and reduce a dataset in a single pipeline. // Chaining methods const sales = [100, 200, 300]; const totalAfterTax = sales .filter(sale => sale > 150) // Filter sales above 150 .map(sale => sale * 1.1) // Apply 10% tax .reduce((acc, sale) => acc + sale, 0); // Sum the sales console.log(totalAfterTax); // 550 5. Adopt Efficient Iteration Techniques Traditional for loops are powerful but prone to off-by-one errors and verbose syntax. Modern iteration tools like for-of loops and object methods simplify iteration significantly. These techniques reduce the potential for error and improve readability. // Array iteration using for-of const fruits = ['apple', 'banana', 'cherry']; for (const fruit of fruits) { console.log(fruit); } // Object iteration using Object.keys const user = { name: 'Alice', age: 25 }; Object.keys(user).forEach(key => { console.log(key, user[key]); }); Also, the Object.entries() method can be used to iterate over both keys and values in an object: // Using Object.entries const user = { name: 'Alice', age: 25 }; for (const [key, value] of Object.entries(user)) { console.log(`${key}: ${value}`); } Warning: Avoid for-in loops for objects as they iterate over inherited properties, potentially leading to unexpected behaviors. Use Object.keys or Object.entries instead. 6. Minimize DOM Interactions Manipulating the DOM can be expensive in terms of performance. Each interaction with the DOM triggers a reflow and repaint, which can severely impact the performance of complex web applications. Minimize direct DOM interactions by batching updates and using techniques like DocumentFragment for complex DOM manipulations. // Inefficient DOM manipulation for (let i = 0; i < 1000; i++) { const div = document.createElement('div'); div.textContent = `Item ${i}`; document.body.appendChild(div); } // Optimized using DocumentFragment const fragment = document.createDocumentFragment(); for (let i = 0; i < 1000; i++) { const div = document.createElement('div'); div.textContent = `Item ${i}`; fragment.appendChild(div); } document.body.appendChild(fragment); Whenever possible, consider using libraries like React or Vue.js,


---
## CosmosDB Performance: Ultimate Optimization Guide

- URL: https://orthogonal.info/maximizing-performance-achieve-lightning-fast-query-speeds-with-these-cosmosdb-optimization-strategies/
- Date: 2022-12-11
- Category: Azure &amp; Cloud
- Summary: Maximize CosmosDB performance with partition key design, indexing policies, query optimization, and throughput tuning. Real-world tips from production use.

Imagine this: your application is growing exponentially, users are engaging daily, and your database queries are starting to drag. What was once a seamless experience has turned into frustrating delays, and your monitoring tools are screaming about query latency. It’s a scenario many developers face when working with CosmosDB, Azure’s globally distributed database service. But here’s the good news: with the right optimization techniques, you can transform CosmosDB into a lightning-fast powerhouse for your applications. we’ll walk you through advanced strategies to optimize CosmosDB performance. From fine-tuning indexing to partitioning like a pro, these tips are battle-tested from real-world experience and designed to help you deliver unparalleled speed and scalability. Warning: Performance means little if your data isn’t secure. Before optimizing, ensure your CosmosDB setup adheres to best practices for security, including private endpoints, access control, and encryption. 1. Choose the Correct SDK and Client 📌 TL;DR: Imagine this: your application is growing exponentially, users are engaging daily, and your database queries are starting to drag. What was once a seamless experience has turned into frustrating delays, and your monitoring tools are screaming about query latency. 🎯 Quick Answer: Optimize CosmosDB by choosing the right partition key to avoid hot partitions, setting indexing policy to exclude unused paths, using point reads (`ReadItemAsync`) instead of queries when possible (10× cheaper in RU cost), and enabling integrated cache for read-heavy workloads. Monitor RU consumption per query to find bottlenecks. I’ve worked with CosmosDB on production services handling millions of daily requests. The pricing model punishes bad partition design ruthlessly — here are the optimizations that actually cut our RU consumption and latency. Starting with the right tools is critical. CosmosDB offers dedicated SDKs across multiple languages, such as Python, .NET, and Java, optimized for its unique architecture. Using generic SQL clients or HTTP requests can severely limit your ability to use advanced features like connection pooling and retry policies. # Using CosmosClient with Python SDK from azure.cosmos import CosmosClient # Initialize client with account URL and key url = "https://your-account.documents.azure.com:443/" key = "your-primary-key" client = CosmosClient(url, credential=key) # Access database and container db_name = "SampleDB" container_name = "SampleContainer" database = client.get_database_client(db_name) container = database.get_container_client(container_name) # Perform optimized query query = "SELECT * FROM c WHERE c.category = 'electronics'" items = container.query_items(query=query, enable_cross_partition_query=True) for item in items: print(item) Using the latest SDK version ensures you benefit from ongoing performance improvements and bug fixes. Pro Tip: Enable connection pooling in your SDK settings to reduce latency caused by repeated connections. 2. Balance Consistency Levels for Speed CosmosDB’s consistency levels—Strong, Bounded Staleness, Session, Consistent Prefix, and Eventual—directly impact query performance. While stronger consistency guarantees accuracy across replicas, it comes at the cost of higher latency. Eventual consistency, on the other hand, offers maximum speed but risks temporary data inconsistencies. Strong Consistency: Ideal for critical applications like banking but slower. Eventual Consistency: Perfect for social apps or analytics where speed matters more than immediate accuracy. # Setting Consistency Level from azure.cosmos import CosmosClient, ConsistencyLevel client = CosmosClient(url, credential=key, consistency_level=ConsistencyLevel.Session) Warning: Misconfigured consistency levels can cripple performance. Evaluate your application’s tolerance for eventual consistency before defaulting to stricter settings. 3. Optimize Partition Keys Partitioning is the backbone of CosmosDB’s scalability. A poorly chosen PartitionKey can lead to hot partitions, uneven data distribution, and bottlenecks. Follow these principles: High Cardinality: Select a key with a large set of distinct values to ensure data spreads evenly across partitions. Query Alignment: Match your PartitionKey to the filters used in your most frequent queries. Avoid Hot Partitions: If one partition key is significantly more active, it may create a “hot partition” that slows down performance. Monitor metrics to ensure even workload distribution. # Defining Partition Key during container creation container_properties = { "id": "SampleContainer", "partitionKey": { "paths": ["/category"], "kind": "Hash" } } database.create_container_if_not_exists( id=container_properties["id"], partition_key=container_properties["partitionKey"], offer_throughput=400 ) Pro Tip: Use Azure’s “Partition Key Metrics” to identify hot partitions. If you spot uneven load, consider updating your partitioning strategy. 4. Fine-Tune Indexing Policies CosmosDB indexes every field by default, which is convenient but often unnecessary. Over-indexing leads to slower write operations. Customizing your IndexingPolicy allows you to focus on fields that matter most for queries. # Setting a custom indexing policy indexing_policy = { "indexingMode": "consistent", "includedPaths": [ {"path": "/name/?"}, {"path": "/category/?"} ], "excludedPaths": [ {"path": "/*"} ] } container_properties = { "id": "SampleContainer", "partitionKey": {"paths": ["/category"], "kind": "Hash"}, "indexingPolicy": indexing_policy } database.create_container_if_not_exists( id=container_properties["id"], partition_key=container_properties["partitionKey"], indexing_policy=indexing_policy, offer_throughput=400 ) Warning: Avoid indexing fields that are rarely queried or used. This can dramatically improve write performance. 5. Leverage Asynchronous Operations Blocking threads is a common source of latency in high-throughput applications. CosmosDB’s SDK supports asynchronous methods that let you execute multiple operations concurrently without blocking threads. # Asynchronous querying example import asyncio from azure.cosmos.aio import CosmosClient async def query_items(): async with CosmosClient(url, credential=key) as client: database = client.get_database_client("SampleDB") container = database.get_container_client("SampleContainer") query = "SELECT * FROM c WHERE c.category = 'electronics'" async for item in container.query_items(query=query, enable_cross_partition_query=True): print(item) asyncio.run(query_items()) Pro Tip: Use asynchronous methods for applications handling large workloads or requiring low-latency responses. 6. Scale Throughput Effectively Provisioning throughput in CosmosDB involves specifying Request Units (RU/s). You can set throughput at the container or database level based on your workload. Autoscale throughput is particularly useful for unpredictable traffic patterns. # Adjusting throughput for a container container.replace_throughput(1000) # Scale to 1000 RU/s Use Azure Monitor to track RU usage and ensure costs remain under control. 7. Reduce Network Overhead with Caching and Batching Network latency can undermine performance. Implement caching mechanisms like PartitionKeyRangeCache to minimize partition lookups. Also, batching operations reduces the number of network calls for high-volume operations. 💡 In practice: On a production service, switching from cross-partition queries to single-partition reads by redesigning our partition key cut RU consumption by 70%. The counterintuitive lesson: sometimes duplicating data across partitions is cheaper than querying across them. Run SELECT * FROM c in the query explorer with diagnostics on — the actual RU cost will surprise you. # Bulk operations for high-volume writes from azure.cosmos import BulkOperationType operations = [ {"operationType": BulkOperationType.Create, "resourceBody": {"id": "1", "category": "electronics"}}, {"operationT


---
## Mastering MySQL Performance: Expert Optimization Techniques

- URL: https://orthogonal.info/maximizing-performance-boost-your-mysql-performance-with-these-proven-optimization-techniques/
- Date: 2022-12-11
- Category: Deep Dives
- Summary: Optimize SQL Server queries with execution plan analysis, index tuning, query rewriting, and parameter sniffing fixes. Practical tips for DBAs and developers.

Introduction: Why MySQL Optimization Matters 📌 TL;DR: Introduction: Why MySQL Optimization Matters Imagine this: your application is running smoothly, users are engaging, and then one day you notice a sudden slowdown. Queries that were once lightning-fast now crawl, frustrating users and sending you scrambling to diagnose the issue. 🎯 Quick Answer: Optimize MySQL performance by adding composite indexes matching your WHERE/ORDER BY clause order, using `EXPLAIN ANALYZE` to identify full table scans, enabling the query cache for read-heavy workloads, and partitioning large tables. Proper indexing alone can improve query speed by 100–1000× on tables with millions of rows. I’ve tuned MySQL instances serving production traffic at scale. Most optimization guides recycle the same generic advice — here are the techniques that actually moved the needle on real workloads. Imagine this: your application is running smoothly, users are engaging, and then one day you notice a sudden slowdown. Queries that were once lightning-fast now crawl, frustrating users and sending you scrambling to diagnose the issue. At the heart of the problem? Your MySQL database has become the bottleneck. If this scenario sounds familiar, you’re not alone. Optimizing MySQL performance isn’t a luxury—it’s a necessity, especially for high-traffic applications or data-intensive platforms. Over my 12+ years working with MySQL, I’ve learned that optimization is both an art and a science. The right techniques can transform your database from sluggish to screaming-fast. I’ll share expert strategies, practical tips, and common pitfalls to help you master MySQL optimization. Understanding the Basics of MySQL Performance Before diving into advanced optimization techniques, it’s important to understand the fundamental factors that influence MySQL performance. A poorly performing database typically boils down to one or more of the following: Query inefficiency: Queries that scan too many rows or don’t leverage indexes efficiently. Server resource limits: Insufficient CPU, memory, or disk I/O capacity to handle the load. Improper schema design: Redundant or unnormalized tables, excessive joins, or lack of indexing. Concurrency issues: Contention for resources when many users access the database simultaneously. Understanding these bottlenecks will help you pinpoint where to focus your optimization efforts. Now, let’s explore specific strategies to improve MySQL performance. Analyzing Query Execution Plans with EXPLAIN Optimization starts with understanding how your queries are executed, and MySQL’s EXPLAIN command is your best friend here. It provides detailed insights into the query execution plan, such as join types, index usage, and estimated row scans. This knowledge is crucial for identifying bottlenecks. -- Example: Using EXPLAIN to analyze a query EXPLAIN SELECT * FROM orders WHERE customer_id = 123 AND order_date > '2023-01-01'; The output of EXPLAIN includes key columns like: type: Indicates the join type. Aim for types like ref or eq_ref for best performance. possible_keys: Lists indexes that could be used for the query. rows: Estimates the number of rows scanned. If you see type = ALL, your query is performing a full table scan—a clear sign of inefficiency. Pro Tip: Always start troubleshooting slow queries with EXPLAIN. It’s the simplest way to uncover inefficient joins or missing indexes. Creating and Optimizing Indexes Indexes are the cornerstone of MySQL performance. They allow the database to locate rows quickly instead of scanning the entire table. However, creating the wrong indexes—or too many—can backfire. -- Example: Creating an index on a frequently queried column CREATE INDEX idx_customer_id ON orders (customer_id); The impact of adding the right index is profound. Consider a table with 10 million rows: Without an index, a query like SELECT * FROM orders WHERE customer_id = 123 might take seconds. With an index, the same query can complete in milliseconds. Warning: Over-indexing can hurt performance. Each index adds overhead for write operations (INSERT, UPDATE, DELETE). Focus on columns frequently used in WHERE clauses, JOINs, or ORDER BY statements. Composite Indexes A composite index covers multiple columns, which can significantly improve performance for queries that filter on or sort by those columns. For example: -- Example: Creating a composite index CREATE INDEX idx_customer_date ON orders (customer_id, order_date); With this index, a query filtering on both customer_id and order_date will be much faster. However, keep the order of columns in mind. The index is most effective when the query filters on the leading column(s). How to Identify Missing Indexes If you’re unsure whether a query would benefit from an index, use the EXPLAIN command to check the possible_keys column. If it’s empty, it’s a sign that no suitable index exists. Also, tools like the slow query log can help you identify queries that might need indexing. Fetching Only the Data You Need Fetching unnecessary rows is a silent killer of database performance. MySQL queries should be designed to retrieve only the data you need, nothing more. The LIMIT clause is your go-to tool for this. -- Example: Fetching the first 10 rows SELECT * FROM orders ORDER BY order_date DESC LIMIT 10; However, using OFFSET with large datasets can degrade performance. MySQL scans all rows up to the offset, even if they’re discarded. Pro Tip: For paginated queries, use a “seek method” with a WHERE clause to avoid large offsets: -- Seek method for pagination SELECT * FROM orders WHERE order_date < '2023-01-01' ORDER BY order_date DESC LIMIT 10; Writing Efficient Joins Joins are powerful but can be a performance minefield if not written carefully. A poorly optimized join can result in massive row scans, slowing your query to a crawl. -- Example: Optimized INNER JOIN SELECT customers.name, orders.total FROM customers INNER JOIN orders ON customers.id = orders.customer_id; Whenever possible, use explicit joins like INNER JOIN instead of filtering with a WHERE clause. MySQL’s optimizer handles explicit joins more effectively. Warning: Always sanitize user inputs in JOIN conditions to prevent SQL injection attacks. Use prepared statements or parameterized queries. Aggregating Data Efficiently Aggregating data with GROUP BY and HAVING can be resource-intensive if not done properly. Misusing these clauses often leads to poor performance. -- Example: Aggregating with GROUP BY and HAVING SELECT customer_id, COUNT(*) AS order_count FROM orders GROUP BY customer_id HAVING order_count > 5; Note the difference between WHERE and HAVING: WHERE filters rows before aggregation. HAVING filters after aggregation. Incorrect usage can lead to inaccurate results or performance degradation. Optimizing Sorting Operations Sorting can be a costly operation, especially on large datasets. Simplify your ORDER BY clauses and avoid complex expressions whenever possible. -- Example: Simple sorting SELECT * FROM orders ORDER BY order_date DESC; If sorting on computed values is unavoidable, consider creating a generated column and indexing it: -- Example: Generated column for sorting ALTER TABLE orders ADD COLUMN order_year INT GENERATED ALWAYS AS (YEAR(order_date)) STORED; CREATE INDEX idx_order_year ON orders (order_year); Guiding the Optimizer with Hints Sometimes, MySQL’s query optimizer doesn’t make the best decisions. In such cases, you can use optimizer hints like FORCE INDEX or STRAIGHT_JOIN to influence its behavior. 💡 In practice: Adding a covering index to our top 3 queries reduced P95 latency from 200ms to 15ms. The trick was using EXPLAIN ANALYZE (new in MySQL 8) to spot that the query was doing a full table scan on a 50M-row table, then building an index that covered all selected columns so it never touched the table data. -- Example: Forcing index usage SELECT * FROM orders FORCE INDEX (idx_customer_id) WHERE customer_id = 123; Warning: Use optimizer h


---
## MySQL 8 vs 7: Key Upgrades and Migration Tips

- URL: https://orthogonal.info/list-of-differences-between-mysql-8-and-mysql-7/
- Date: 2022-12-11
- Category: Deep Dives
- Summary: Compare MySQL 8 and MySQL 7 features including CTEs, window functions, JSON improvements, and default authentication changes. Plus migration tips.

Why MySQL 8 is a Major improvement for Modern Applications 📌 TL;DR: Why MySQL 8 is a Major improvement for Modern Applications If you’ve been managing databases with MySQL 7, you might be wondering whether upgrading to MySQL 8 is worth the effort. Spoiler alert: it absolutely is. 🎯 Quick Answer: MySQL 8 replaces MySQL 5.7 (there is no MySQL 7) with major upgrades: window functions, CTEs, native JSON improvements, invisible indexes, and the default authentication plugin changed to `caching_sha2_password`. Before migrating, run `mysqlcheck –check-upgrade` and test authentication compatibility with your application drivers. I’ve migrated production MySQL instances from 5.7 to 8.0 — and the performance gains were real, but so were the migration headaches. Here’s what actually changed under the hood and how to avoid the pitfalls I hit. If you’ve been managing databases with MySQL 7, you might be wondering whether upgrading to MySQL 8 is worth the effort. Spoiler alert: it absolutely is. MySQL 8 isn’t just a version update; it’s a significant overhaul designed to address the limitations of its predecessor while introducing powerful new features. From enhanced performance and security to modern SQL capabilities, MySQL 8 empowers developers and database administrators to build stronger, scalable, and efficient applications. However, with change comes complexity. Migrating to MySQL 8 involves understanding its new features, default configurations, and potential pitfalls. This guide will walk you through the most significant differences, showcase practical examples, and offer tips to ensure a smooth transition. By the end, you’ll not only be ready to upgrade but also confident in harnessing everything MySQL 8 has to offer. Enhanced Default Configurations: Smarter Out of the Box One of the most noticeable changes in MySQL 8 is its smarter default configurations, which align with modern database practices. These changes help reduce manual setup and improve performance, even for newcomers. Let’s examine two major default upgrades: the storage engine and character set. Default Storage Engine: Goodbye MyISAM, Hello InnoDB In MySQL 7, the default storage engine was MyISAM, which is optimized for read-heavy workloads but lacks critical features like transaction support and crash recovery. MySQL 8 replaces this with InnoDB, making it the de facto engine for most use cases. CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, product_name VARCHAR(100) NOT NULL, order_date DATETIME NOT NULL ); -- Default storage engine is now InnoDB in MySQL 8 InnoDB supports ACID compliance, ensuring data integrity even during system crashes or power failures. It also enables row-level locking, which is essential for high-concurrency applications like e-commerce sites, financial systems, and collaborative platforms. Warning: Existing MyISAM tables won’t automatically convert to InnoDB during an upgrade. Use the ALTER TABLE command to manually migrate them: ALTER TABLE orders ENGINE=InnoDB; For those running legacy applications with MyISAM tables, this migration step is critical. Failure to update could limit your ability to take advantage of MySQL 8’s advanced features, such as transaction guarantees and crash recovery. Character Set and Collation: Full Unicode Support MySQL 8 sets utf8mb4 as the default character set and utf8mb4_0900_ai_ci as the default collation. This upgrade ensures full Unicode support, including emojis, non-Latin scripts, and complex character sets used in various global languages. CREATE TABLE messages ( id INT AUTO_INCREMENT PRIMARY KEY, content TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL ); Previously, MySQL 7 defaulted to latin1, which couldn’t handle many modern text characters. This made it unsuitable for applications with international audiences. With Unicode support, developers can now create truly global applications without worrying about garbled text or unsupported characters. Pro Tip: For existing databases using latin1, run this query to identify incompatible tables: SELECT table_schema, table_name FROM information_schema.tables WHERE table_collation LIKE 'latin1%'; Once identified, you can convert tables to utf8mb4 with a command like: ALTER TABLE messages CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; SQL Features That Simplify Complex Querying MySQL 8 introduces several new SQL features that reduce the complexity of writing advanced queries. These enhancements simplify operations, improve developer productivity, and make code more maintainable. Window Functions Window functions allow you to perform calculations across a set of rows without grouping them. This is particularly useful for ranking, cumulative sums, and moving averages. SELECT employee_id, department, salary, RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rank FROM employees; In MySQL 7, achieving this required nested subqueries or manual calculations, which were both cumbersome and error-prone. Window functions simplify this process immensely, benefiting reporting tools, dashboards, and analytical queries. For instance, an e-commerce application can now easily rank products by sales within each category: SELECT product_id, category, sales, RANK() OVER (PARTITION BY category ORDER BY sales DESC) AS category_rank FROM product_sales; Common Table Expressions (CTEs) CTEs improve the readability of complex queries by allowing you to define temporary result sets. They’re especially useful for breaking down multi-step operations into manageable chunks. WITH SalesSummary AS ( SELECT department, SUM(sales) AS total_sales FROM sales_data GROUP BY department ) SELECT department, total_sales FROM SalesSummary WHERE total_sales > 100000; CTEs make it easy to debug and maintain queries over time, a feature sorely missing in MySQL 7. They also eliminate the need for repetitive subqueries, improving both performance and readability. JSON Enhancements JSON handling in MySQL 8 has been vastly improved, making it easier to work with semi-structured data. For instance, the JSON_TABLE() function converts JSON data into a relational table format. SET @json_data = '[ {"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"} ]'; SELECT * FROM JSON_TABLE(@json_data, '$[*]' COLUMNS ( id INT PATH '$.id', name VARCHAR(50) PATH '$.name' )) AS jt; This eliminates the need for manual parsing, saving time and reducing errors. For applications that rely heavily on APIs returning JSON data, such as social media analytics or IoT platforms, this enhancement is a major productivity boost. Security Upgrades: Stronger and Easier to Manage Security is a top priority in MySQL 8, with several new features aimed at simplifying user management and enhancing data protection. Role-Based Access Control Roles allow you to group permissions and assign them to users. This is particularly useful in large organizations with complex access requirements. CREATE ROLE 'read_only'; GRANT SELECT ON my_database.* TO 'read_only'; GRANT 'read_only' TO 'analyst1'; In MySQL 7, permissions had to be assigned on a per-user basis, which was both tedious and error-prone. By implementing roles, MySQL 8 simplifies user management, especially in environments with frequent staff changes or evolving project requirements. Default Password Policy MySQL 8 enforces stronger password policies by default. For example, passwords must meet a certain complexity level, reducing the risk of brute-force attacks. Pro Tip: Use the validate_password plugin to customize password policies: SET GLOBAL validate_password.policy = 'STRONG'; Performance Optimizations MySQL 8 includes several performance enhancements that can significantly speed up database operations. Invisible Indexes Invisible indexes allow you to test the impact of index changes without affecting query execution. This is ideal for performance tuning. ALTER TABLE employees ADD INDEX idx_name (name) INVISIBLE; You can make the index visible again once testing i


---
## Text-to-Speech in JavaScript: A Complete Guide

- URL: https://orthogonal.info/how-to-implement-text-to-speech-in-javascript/
- Date: 2022-12-11
- Category: JavaScript
- Summary: Add text-to-speech to your web app using the JavaScript Speech Synthesis API. Covers voice selection, rate control, events, and cross-browser support.

Why Giving Your Web App a Voice Changes Everything 📌 TL;DR: Why Giving Your Web App a Voice Changes Everything Picture this: you’re developing a fitness app. It offers personalized workout plans, tracks user progress, and even calculates calories burned. But something’s missing—its ability to engage users in a truly interactive way. 🎯 Quick Answer: Implement text-to-speech in JavaScript using the built-in Web Speech API: call `new SpeechSynthesisUtterance(‘text’)` and `window.speechSynthesis.speak(utterance)`. Customize voice, rate (0.1–10), and pitch (0–2). It works in all modern browsers with no external dependencies or API keys required. I build browser-based tools that need to work across devices without server dependencies. The Web Speech API is surprisingly capable for text-to-speech — I’ve used it in accessibility features and notification systems. Here’s a practical implementation guide. Picture this: you’re developing a fitness app. It offers personalized workout plans, tracks user progress, and even calculates calories burned. But something’s missing—its ability to engage users in a truly interactive way. Now, imagine your app giving vocal encouragement: “Keep going! You’re doing great!” or “Workout complete, fantastic job!” Suddenly, the app feels alive, motivating, and accessible to a broader audience, including users with disabilities or those who prefer auditory feedback. This is the transformative power of text-to-speech (TTS). With JavaScript’s native speechSynthesis API, you can make your web application speak without relying on third-party tools or external libraries. While the basics are straightforward, mastering this API requires understanding its nuances, handling edge cases, and optimizing for performance. Let me guide you through everything you need to know about implementing TTS in JavaScript. Getting Started with the speechSynthesis API The speechSynthesis API is part of the Web Speech API, and it’s built directly into modern browsers. It allows developers to convert text into spoken words using the speech synthesis engine available on the user’s device. This makes it lightweight and eliminates the need for additional installations. The foundation of this API lies in the SpeechSynthesisUtterance object, which represents the text to be spoken. This object lets you customize various parameters like language, pitch, rate, and voice. Let’s start with a simple example: Basic Example: Making Your App Speak Here’s a straightforward implementation: // Check if speech synthesis is supported if ('speechSynthesis' in window) { // Create a SpeechSynthesisUtterance instance const utterance = new SpeechSynthesisUtterance(); // Set the text to be spoken utterance.text = "Welcome to our app!"; // Speak the utterance speechSynthesis.speak(utterance); } else { console.error("Speech synthesis is not supported in this browser."); } When you run this snippet, the browser will vocalize “Welcome to our app!” It’s simple, but let’s dig deeper to ensure this feature works reliably in real-world applications. Customizing Speech Output While the default settings suffice for basic use, customizing the speech output can dramatically improve user experience. Below are the key properties you can adjust: 1. Selecting Voices The speechSynthesis.getVoices() method retrieves the list of voices supported by the user’s device. You can use this to select a specific voice: speechSynthesis.addEventListener('voiceschanged', () => { const voices = speechSynthesis.getVoices(); if (voices.length > 0) { // Create an utterance const utterance = new SpeechSynthesisUtterance("Hello, world!"); // Set the voice to the second available option utterance.voice = voices[1]; // Speak the utterance speechSynthesis.speak(utterance); } else { console.error("No voices available!"); } }); Pro Tip: Voice lists might take time to load. Always use the voiceschanged event to ensure the list is ready. 2. Adjusting Pitch and Rate Tuning the pitch and rate can make the speech sound more natural or match your application’s tone: pitch: Controls the tone, ranging from 0 (low) to 2 (high). Default is 1. rate: Controls the speed, with values between 0.1 (slow) and 10 (fast). Default is 1. // Create an utterance const utterance = new SpeechSynthesisUtterance("Experimenting with pitch and rate."); // Set pitch and rate utterance.pitch = 1.8; // Higher pitch utterance.rate = 0.8; // Slower rate // Speak the utterance speechSynthesis.speak(utterance); 3. Adding Multilingual Support To cater to a global audience, you can set the lang property for proper pronunciation: // Create an utterance const utterance = new SpeechSynthesisUtterance("Hola, ¿cómo estás?"); // Set language to Spanish (Spain) utterance.lang = 'es-ES'; // Speak the utterance speechSynthesis.speak(utterance); Using the appropriate language code ensures the speech engine applies the correct phonetics and accents. Warning: Not all devices support all languages. Test your app on multiple platforms to avoid surprises. Advanced Features to Enhance Your TTS Implementation Queueing Multiple Utterances Need to deliver multiple sentences in sequence? The speechSynthesis API queues utterances automatically: // Create multiple utterances const utterance1 = new SpeechSynthesisUtterance("This is the first sentence."); const utterance2 = new SpeechSynthesisUtterance("This is the second sentence."); const utterance3 = new SpeechSynthesisUtterance("This is the third sentence."); // Speak all utterances in sequence speechSynthesis.speak(utterance1); speechSynthesis.speak(utterance2); speechSynthesis.speak(utterance3); Pausing and Resuming Speech Control playback with pause and resume functionality: // Create an utterance const utterance = new SpeechSynthesisUtterance("This sentence will be paused midway."); // Speak the utterance speechSynthesis.speak(utterance); // Pause after 2 seconds setTimeout(() => { speechSynthesis.pause(); console.log("Speech paused."); }, 2000); // Resume after another 2 seconds setTimeout(() => { speechSynthesis.resume(); console.log("Speech resumed."); }, 4000); Stopping Speech Need to cancel ongoing speech? Use the cancel method: // Immediately stop all ongoing speech speechSynthesis.cancel(); Troubleshooting Common Pitfalls Voice List Delays: The voice list might not populate immediately. Always use the voiceschanged event. Language Compatibility: Test multilingual support on various devices to ensure proper pronunciation. Browser Variability: Safari, especially on iOS, has inconsistent TTS behavior. Consider fallback options. Pro Tip: Implement feature detection to check if the speechSynthesis API is supported before using it: if ('speechSynthesis' in window) { console.log("Speech synthesis is supported!"); } else { console.error("Speech synthesis is not supported in this browser."); } Accessibility and Security Considerations Ensuring Accessibility TTS can enhance accessibility, but it should complement other features like ARIA roles and keyboard navigation. This ensures users with diverse needs can interact smoothly with your app. Securing Untrusted Input Be cautious with user-generated text. While the speechSynthesis API doesn’t execute code, unsanitized input can introduce vulnerabilities elsewhere in your application. Performance and Compatibility The speechSynthesis API works in most modern browsers, including Chrome, Edge, and Firefox. However, Safari’s implementation can be less reliable, particularly on iOS. Always test across multiple browsers and devices to verify compatibility. 💡 In practice: Cross-browser voice consistency is the biggest pain point. Chrome and Safari return completely different voice lists, and some voices vanish between OS updates. I always implement a fallback chain: preferred voice → same language voice → default voice. Never hardcode a voice name — I learned this when a Chrome update removed ‘Google US English’ and broke my app for a week. Quick Summary The speechSynthesis API enables


---
## C# Performance: Using const and readonly Effectively

- URL: https://orthogonal.info/maximizing-performance-improve-performance-of-your-c-code-with-the-const-and-readonly-keywords/
- Date: 2022-12-11
- Category: C# &amp; .NET
- Summary: Boost C# performance with const and readonly. Learn when to use each, how the compiler optimizes them, and real benchmarks showing the speed difference.

The Power of Immutability in C# 📌 TL;DR: The Power of Immutability in C# Imagine this scenario: you’re on call, and your application crashes during peak hours. After hours of debugging, you discover that a supposedly constant value has been modified deep in your codebase. A lack of proper immutability enforcement. 🎯 Quick Answer: In C#, use `const` for compile-time constants (inlined at build, zero runtime cost) and `readonly` for values set once at runtime in the constructor. Prefer `const` for truly fixed values like math constants; use `readonly` for dependency-injected or configuration-based values that shouldn’t change after initialization. After profiling .NET services under heavy production load, I’ve learned that const and readonly aren’t just code hygiene — they’re real performance tools when used correctly. Here’s what the JIT compiler actually does with them. Imagine this scenario: you’re on call, and your application crashes during peak hours. After hours of debugging, you discover that a supposedly constant value has been modified deep in your codebase. The culprit? A lack of proper immutability enforcement. This is where the const and readonly keywords in C# shine. They aren’t just about preventing bugs; they can help optimize your application’s performance and enhance code clarity. Over the years, I’ve learned that understanding and Using these keywords is essential for writing solid and maintainable software. Let me walk you through their nuances, practical applications, and some gotchas that could trip you up if you’re not careful. Understanding const: Compile-Time Constants The const keyword in C# is used to define values that are immutable and known at compile time. Think of it as defining something etched in stone—once declared, it cannot be changed. The compiler replaces every reference to a const with its literal value during compilation, which eliminates runtime lookups. public class MathConstants { // A compile-time constant public const double Pi = 3.14159265359; // Another example public const int MaxUsers = 100; } In the example above, whenever you reference MathConstants.Pi or MathConstants.MaxUsers, the compiler substitutes these references with their actual values. This substitution boosts runtime performance, especially in scenarios where these values are accessed frequently. Pro Tip: Use const for values that are truly immutable, such as mathematical constants or application-wide configuration values that will never change. Limitations and Potential Pitfalls While const is incredibly useful, it does have limitations. One major drawback is its rigidity—any changes to a const require recompiling all assemblies that depend on it. This can become a maintenance headache in large projects or shared libraries. Warning: Avoid using const for values that might need updates, such as configuration settings or business rules. Instead, consider readonly for these scenarios. Diving Into readonly: Runtime Constants The readonly keyword provides a more flexible alternative to const. Unlike const, readonly fields are initialized either at the point of declaration or within the constructor of the class. This makes them ideal for values that are immutable but can only be determined at runtime. public class AppConfig { // A readonly field public readonly string ApiKey; // Initialize readonly field in the constructor public AppConfig() { ApiKey = Environment.GetEnvironmentVariable("API_KEY") ?? throw new InvalidOperationException("API_KEY not set"); } } Here, the ApiKey field is immutable after initialization, but its value is determined at runtime by reading an environment variable. Unlike const, readonly fields are stored as instance or static fields, depending on how they are declared. Performance Considerations While accessing readonly fields involves a slight overhead compared to const, the difference is negligible for most applications. The trade-off is the added flexibility of runtime initialization, which can be indispensable for certain scenarios. Pro Tip: Use readonly for values that are immutable but initialized at runtime, such as API keys, database connection strings, or settings loaded from configuration files. Comparing const and readonly Side by Side To clarify their differences, here’s a side-by-side comparison of const and readonly: Feature const readonly Initialization At declaration only At declaration or in constructor Compile-Time Substitution Yes No Performance Faster (no runtime lookup) Slightly slower (runtime lookup) Flexibility Less flexible More flexible Real-World Example: Hybrid Configurations Let’s consider a scenario where both keywords are leveraged effectively. Imagine you’re developing a web application that connects to an external API. You have a base URL that never changes and an API key that is loaded dynamically during runtime. public class ApiConfig { // Base URL: compile-time constant public const string BaseUrl = "https://api.example.com"; // API key: runtime constant public readonly string ApiKey; public ApiConfig() { ApiKey = Environment.GetEnvironmentVariable("API_KEY") ?? throw new InvalidOperationException("API_KEY is missing"); } } Here, BaseUrl is declared as a const since its value is fixed and will never change. On the other hand, ApiKey is declared as readonly because its value depends on the runtime environment. Warning: Do not hardcode sensitive information like API keys into your application. Use environment variables or secure storage solutions to safeguard these values. Advanced Applications of Immutability Immutability isn’t limited to const and readonly. Using immutability extends to other areas of C#, such as creating immutable objects using properties or using immutable collections. These techniques can help reduce side effects and improve the predictability of your code. Using Immutable Objects Immutable objects don’t allow changes to their state once they are created. For example: public class ImmutableUser { public string Name { get; } public int Age { get; } public ImmutableUser(string name, int age) { Name = name; Age = age; } } Here, the ImmutableUser class ensures that its properties cannot be modified after initialization. This provides thread-safety and eliminates side effects. Immutable Collections C# provides immutable collections out of the box, such as ImmutableArray and ImmutableList. These collections are especially useful in functional programming paradigms or when dealing with concurrent applications. using System.Collections.Immutable; var immutableList = ImmutableList.Create("Apple", "Banana", "Cherry"); // Attempting to modify will result in a compiler error // immutableList.Add("Date"); Immutable collections are perfect for scenarios where data integrity and thread-safety are paramount. Troubleshooting Common Issues Even experienced developers can stumble when working with const and readonly. Here are some common issues and how to resolve them: 💡 In practice: On a hot-path calculation service, replacing static readonly with const for our magic numbers let the JIT inline the values directly. The benchmark showed a 15% throughput improvement on that method. Small win, but it was free — just changing two keywords. Issue: Updating a const value doesn’t affect dependent assemblies. Solution: Ensure all dependent assemblies are recompiled whenever a const is changed. Issue: Attempting to assign a value to a readonly field outside its declaration or constructor. Solution: Restrict assignments to the declaration or constructor only. Issue: Using readonly for frequently accessed values in performance-critical code. Solution: Favor const for high-performance scenarios where immutability is guaranteed. Quick Summary Use const for values that are immutable and known at compile time. Leverage readonly for values that are immutable but require runtime initialization. Explore immutability beyond const and readonly, such as immutable objects and collections. Be aware of th


---
## C# Performance Deep Dive: Value Types vs Reference Types

- URL: https://orthogonal.info/maximizing-performance-use-value-types-instead-of-reference-types-in-c-for-performance-improvement/
- Date: 2022-12-11
- Category: C# &amp; .NET
- Summary: Understand JavaScript hoisting for var, let, const, and functions. Clear examples showing the temporal dead zone, common pitfalls, and best practices.

Imagine this scenario: your C# application, once zippy and efficient, has slowed to a crawl. Memory consumption is through the roof, and the garbage collector is working overtime. You open your trusty profiler, and the diagnosis is clear—GC pressure from an excessive heap allocation. The culprit? Over-reliance on reference types where value types might have sufficed. This isn’t just a theoretical concern; choosing between value types and reference types can profoundly impact your application’s performance and memory efficiency. Let’s unravel the mechanics, benefits, and trade-offs associated with these two fundamental concepts in C#. What Are Value Types and Reference Types? 📌 TL;DR: Imagine this scenario: your C# application, once zippy and efficient, has slowed to a crawl. Memory consumption is through the roof, and the garbage collector is working overtime. You open your trusty profiler, and the diagnosis is clear—GC pressure from an excessive heap allocation. 🎯 Quick Answer: C# value types (structs) are stored on the stack and avoid GC heap allocation, making them ideal for small, frequently created objects under 16 bytes. Reference types (classes) live on the heap and incur GC pressure. Switching hot-path data from class to struct can reduce GC pauses by 30–50%. After profiling .NET services handling 10K+ requests per second, I can tell you that the value type vs. reference type decision is the single biggest performance lever most C# developers ignore. Here’s what actually matters, based on real production profiling. In C#, every type falls into one of two core categories: value types and reference types. This classification fundamentally determines how data is stored, accessed, and managed in memory. Let’s explore both in detail. Value Types Value types are defined using the struct keyword and are typically stored on the stack. When you assign a value type to a new variable or pass it to a method, a copy is created. This behavior ensures that changes to one instance do not affect others. struct Point { public int X; public int Y; } Point p1 = new Point { X = 10, Y = 20 }; Point p2 = p1; // Creates a copy of p1 p2.X = 30; Console.WriteLine(p1.X); // Output: 10 In this example, modifying p2 does not impact p1 because they are independent copies of the same data. Value types include primitive types such as int, double, and bool, as well as user-defined structs. They are ideal for small, immutable data structures where performance is critical. Reference Types Reference types, defined using the class keyword, are stored on the heap. Variables of reference types hold a reference (think of it as a pointer) to the actual data. Assigning a reference type to another variable or passing it to a method copies the reference, not the data itself. class Circle { public double Radius; } Circle c1 = new Circle { Radius = 5.0 }; Circle c2 = c1; // Copies the reference, not the data c2.Radius = 10.0; Console.WriteLine(c1.Radius); // Output: 10.0 Here, changing c2 also alters c1, as both variables point to the same object in memory. Reference types include objects, strings, arrays, and even delegates. They are better suited for complex data structures and scenarios where objects need to be shared or modified by multiple parts of your application. Pro Tip: Use value types for small, immutable data structures like 2D points or colors. For larger, mutable objects, reference types are generally more appropriate. Performance Implications: Stack vs Heap The performance differences between value and reference types boil down to how memory management operates in C#: the stack versus the heap. Stack: Fast, contiguous memory used for short-lived data like local variables. Data on the stack is automatically cleaned up when it goes out of scope. Heap: Slower, fragmented memory for long-lived objects. Memory here is managed by the garbage collector, introducing potential performance overhead. Understanding these differences can help you optimize your application for speed and efficiency. Let’s dive deeper into how these memory models work in practice. Code Example: Measuring Performance Let’s compare the performance of value types and reference types using a benchmark: using System; using System.Diagnostics; struct ValuePoint { public int X; public int Y; } class ReferencePoint { public int X; public int Y; } class Program { static void Main() { const int iterations = 10_000_000; // Benchmark value type Stopwatch sw = Stopwatch.StartNew(); ValuePoint vp = new ValuePoint(); for (int i = 0; i < iterations; i++) { vp.X = i; vp.Y = i; } sw.Stop(); Console.WriteLine($"Value type time: {sw.ElapsedMilliseconds} ms"); // Benchmark reference type sw.Restart(); ReferencePoint rp = new ReferencePoint(); for (int i = 0; i < iterations; i++) { rp.X = i; rp.Y = i; } sw.Stop(); Console.WriteLine($"Reference type time: {sw.ElapsedMilliseconds} ms"); } } On most systems, the value type version executes significantly faster due to the stack’s efficiency compared to heap allocation and garbage collection. However, this advantage diminishes when value types grow in size. Warning: Large structs can cause excessive copying, negating the performance benefits of stack allocation. Always profile your application to ensure the expected gains. Memory Management Challenges Understanding the nuances of memory management is critical when deciding between value and reference types. Here are some common challenges to consider: Boxing and Unboxing When a value type is treated as an object (e.g., added to a non-generic collection like ArrayList), it undergoes “boxing,” which involves heap allocation. Conversely, retrieving the value involves “unboxing,” which adds runtime overhead. int x = 42; object obj = x; // Boxing int y = (int)obj; // Unboxing Pro Tip: Use generic collections like List<T> to avoid unnecessary boxing and unboxing when working with value types. Mutable Value Types Mutable value types can lead to subtle bugs, especially in collections. Consider this example: struct Point { public int X; public int Y; } var points = new List<Point> { new Point { X = 1, Y = 2 } }; points[0].X = 3; // This won't modify the original struct in the list! Why? Because the Point value is copied when accessed. To avoid such surprises, make value types immutable whenever possible. When to Choose Value Types Value types are not a silver bullet. They shine in specific scenarios, such as: Small, self-contained data: Examples include points, vectors, and dimensions. Immutability: Immutable value types prevent inadvertent state changes. Performance-critical code: Value types minimize heap allocations and improve cache locality. When to Avoid Value Types However, there are situations where reference types are the better choice: Complex or large data: Large structs result in excessive copying, reducing performance. Shared or mutable state: Use reference types when multiple components need to share and modify the same data. Inheritance requirements: Value types don’t support polymorphism, so reference types are necessary for inheritance hierarchies. Advanced Considerations When working with modern C#, you may encounter advanced features like records and Span<T>, which blur the lines between value and reference types. For instance, Span<T> provides stack-only value type semantics for working with memory, offering performance benefits while maintaining safety. 💡 In practice: Switching from class to struct for our hot-path DTOs cut GC pressure by 40% on a service processing financial data. But beware: structs larger than 16 bytes get copied on every method call, which can quietly kill performance. I always benchmark both paths with BenchmarkDotNet before committing. Quick Summary Value types are efficient for small, immutable data, while reference types excel with complex, shared, or mutable objects. Understand and measure the trade-offs, especially around memory allocation and copying overhead. Leverage generic collections t


---
## C# Fixed Keyword: Memory Stability & Performance

- URL: https://orthogonal.info/maximizing-performance-use-the-fixed-keyword-to-prevent-the-garbage-collector-from-moving-managed-objects-in-memory-to-improve-perf/
- Date: 2022-12-07
- Category: C# &amp; .NET
- Summary: Use the C# fixed keyword to pin objects in memory and avoid GC relocation. Learn unsafe pointer access, performance gains, and when fixed is appropriate.

Why Memory Control Can Make or Break Your Application 📌 TL;DR: Why Memory Control Can Make or Break Your Application Imagine this: you’re developing a high-performance system processing millions of data points in real-time. Everything seems fine during initial testing, but as load increases, you start noticing erratic latency spikes. 🎯 Quick Answer: The C# `fixed` keyword pins an object in memory so the garbage collector won’t relocate it, enabling safe pointer operations in `unsafe` code. Use it when interoperating with native APIs or performing direct memory manipulation. Pinning is essential for high-performance scenarios like image processing and hardware buffers. After profiling .NET services handling 10K+ requests per second at Big Tech, I can tell you that the fixed keyword is one of the most misunderstood performance tools in C#. Here’s when it actually matters — and when it doesn’t. Imagine this: you’re developing a high-performance system processing millions of data points in real-time. Everything seems fine during initial testing, but as load increases, you start noticing erratic latency spikes. The culprit? Garbage collection (GC) pauses. These pauses occur when the GC rearranges objects in memory for optimization, but this “helpful” process can wreak havoc on time-sensitive applications. When faced with such problems, you need tools that let you wrest control back from the garbage collector. One such tool in C# is the fixed keyword. It allows you to “pin” objects in memory, ensuring their address remains stable. This is invaluable for scenarios involving pointers, unmanaged APIs, or performance-critical operations. I’ll guide you through the ins and outs of the fixed keyword. We’ll explore its functionality, best practices, and potential pitfalls. By the end, you’ll understand how—and when—to use this powerful feature effectively. Understanding the fixed Keyword The fixed keyword is designed for one specific purpose: to pin an object in memory. Normally, the garbage collector is free to move objects to optimize memory usage. While this is fine for most applications, it’s problematic when you need stable memory addresses—such as when working with pointers or calling unmanaged code. Pinning an object ensures its memory address remains unchanged for the duration of a fixed block. This makes it possible to perform low-level operations without worrying about the GC relocating your data mid-execution. However, there’s a trade-off: pinning objects can hinder garbage collection efficiency, as pinned objects can’t be relocated. This is why fixed should be reserved for scenarios where stability is critical. Example Syntax Here’s a simple illustration of how the fixed keyword works: unsafe { int[] numbers = new int[] { 1, 2, 3, 4, 5 }; fixed (int* p = numbers) { for (int i = 0; i < numbers.Length; i++) { Console.WriteLine($"Value at index {i}: {p[i]}"); } } } Key points to note: The fixed block pins the numbers array in memory, preventing the GC from moving it. The pointer p provides direct access to the array’s memory. Once the fixed block ends, the object is unpinned, and the GC regains control. Pro Tip: Always limit the scope of your fixed blocks. The shorter the block, the less impact on the garbage collector. Real-World Applications of the fixed Keyword Let’s explore scenarios where fixed can be a big improvement: Interop with Unmanaged Code When working with native APIs—such as those in Windows or third-party libraries—you often need to pass pointers to managed objects. Without fixed, the GC could relocate the object, invalidating the pointer. Here’s an example: unsafe { byte[] buffer = new byte[256]; fixed (byte* pBuffer = buffer) { // Call an unmanaged function, passing the pointer NativeApi.WriteToBuffer(pBuffer, buffer.Length); } } In this case, fixed ensures the buffer’s memory address remains constant while the unmanaged code operates on it. High-Performance Array Operations For applications like real-time simulations or game engines, every millisecond counts. Using fixed with pointers can minimize overhead by bypassing bounds checking and method calls: unsafe { float[] data = new float[1000000]; fixed (float* pData = data) { for (int i = 0; i < data.Length; i++) { pData[i] = MathF.Sin(i); // Direct memory access } } } While this approach isn’t suitable for most applications, it’s ideal for performance-critical tasks like large-scale numerical computations. Working with Hardware or Devices In scenarios where you’re interacting with hardware devices, such as sensors or peripheral hardware, you may need to handle memory manually. For example, if you’re implementing a driver or working with a low-level API for a device, you’ll often need to pass memory buffers to the hardware. By using the fixed keyword, you can ensure the memory remains stable while the hardware accesses it: unsafe { byte[] deviceBuffer = new byte[1024]; fixed (byte* pDeviceBuffer = deviceBuffer) { // Pass the buffer to a hardware driver API DeviceDriver.SendData(pDeviceBuffer, deviceBuffer.Length); } } This approach is widely used in situations where performance and stability are critical, such as in embedded systems or custom hardware solutions. Performance Considerations So, how much faster is fixed? The answer depends on the context. In tight loops or interop scenarios, you might see significant gains—sometimes up to 20% faster than equivalent managed code. However, this comes at the cost of increased complexity and reduced flexibility. It’s essential to profile your code to determine whether fixed provides measurable benefits. Blindly replacing managed code with unsafe constructs often leads to diminishing returns. Another factor to consider is the impact on the garbage collector. A pinned object can block the GC from compacting the heap, which may increase memory fragmentation. If too many objects are pinned at once, the performance of the entire application can degrade. Warning: Pinning too many objects simultaneously can lead to heap fragmentation, degrading garbage collection performance. Common Pitfalls and How to Avoid Them While fixed is powerful, it’s not without risks. Here are some common mistakes developers make: Overusing fixed: Pinning objects indiscriminately can impact overall application performance. Improper pointer arithmetic: Miscalculations can lead to memory corruption or crashes. Ignoring scope limitations: Always ensure fixed blocks are as short as possible. Memory leaks: If you pass pointers to unmanaged code without proper cleanup, you risk memory leaks. Concurrency issues: Be cautious when using fixed in multithreaded environments, as pinned objects may introduce synchronization challenges. To avoid these issues, follow best practices and thoroughly test unsafe sections of your code. Use profiling and debugging tools to catch potential problems early. When to Use—and Avoid—the fixed Keyword fixed is a specialized tool that shines in the right circumstances but can cause problems when misused. Here’s a quick guide: 💡 In practice: On a high-throughput service I profiled, pinning a frequently-accessed byte array with fixed during serialization cut P99 latency by 12%. But I’ve also seen developers pin objects unnecessarily, which actually increases GC pressure by fragmenting the heap. Profile first, pin second. Use fixed For: Interop with unmanaged APIs: Passing pointers to native code. Performance-critical operations: Optimizing tight loops or large datasets. Low-level memory manipulation: Situations where managed abstractions are insufficient. Hardware interaction: Working with devices or embedded systems. Avoid fixed For: General-purpose code: Managed solutions are safer and easier to maintain. Collaborative projects: Unsafe code increases the learning curve for contributors. Security-sensitive applications: Pointer misuse can introduce vulnerabilities. Long-lived pinning: Avoid pinning objects for extended periods, as this can disrupt garb


---
## Stochastic Oscillator in JavaScript for Scalping

- URL: https://orthogonal.info/javascript-finance-stochastic-oscillator-for-scalping-buy-sell/
- Date: 2022-12-07
- Category: Finance &amp; Trading
- Summary: Implement the Stochastic Oscillator in JavaScript for crypto and stock scalping. Covers %K, %D calculation, signal generation, and backtesting logic.

Why the Stochastic Oscillator is a Major improvement for Scalpers 📌 TL;DR: Why the Stochastic Oscillator is a Major improvement for Scalpers Picture this: the stock you’re watching is moving rapidly, bouncing between highs and lows in a matter of minutes. 🎯 Quick Answer: Implement the stochastic oscillator in JavaScript by calculating %K as `((close – lowestLow) / (highestHigh – lowestLow)) * 100` over a 14-period window, then smooth with a 3-period SMA for %D. For scalping, enter long when %K crosses above %D below 20, and short when it crosses below %D above 80. I’ve backtested the stochastic oscillator across thousands of 1-minute candles in my trading system. It’s one of the few momentum indicators that actually holds up for scalping — if you tune the parameters right. Here’s how I implement it in JavaScript. Picture this: the stock you’re watching is moving rapidly, bouncing between highs and lows in a matter of minutes. As a scalper, you live for these moments—but making the right decision about when to buy or sell can feel like threading a needle during an earthquake. That’s where the stochastic oscillator shines. It’s a powerful momentum indicator designed to identify overbought and oversold conditions, helping you make informed, data-driven trading decisions. Scalping is a high-pressure trading style that thrives on quick decisions and small price movements. To succeed, scalpers need tools that deliver instant insights, and the stochastic oscillator fulfills this need by providing real-time momentum analysis. Whether you’re a seasoned scalper or a beginner, understanding and Using this indicator can significantly improve your profitability and decision-making. we’re not just scratching the surface. We’ll dive deep into the mechanics of the stochastic oscillator, its implementation in JavaScript, how to optimize it for different scenarios, and strategies to pair it with other indicators. You’ll also learn how to troubleshoot common issues and avoid pitfalls that often trip up new traders. Pro Tip: The stochastic oscillator works best in sideways or range-bound markets. Pair it with a trend-following indicator like the moving average to improve accuracy when trading in trending markets. Understanding the Stochastic Oscillator The stochastic oscillator is a momentum indicator that compares an asset’s closing price to its price range over a specified period. It outputs a percentage ranging from 0 to 100, making it easy to gauge the asset’s momentum at a glance: Below 20: Indicates an oversold condition, which could signal a buying opportunity. Above 80: Indicates an overbought condition, which could signal a selling opportunity. Unlike other indicators such as the Relative Strength Index (RSI), which focuses on the rate of price change, the stochastic oscillator emphasizes the relationship between closing prices and the high-low range of an asset. This distinction makes it particularly effective for scalping, where traders aim to make profits from small price movements. How the Stochastic Oscillator Works The stochastic oscillator has two key components: %K: The primary value, calculated as %K = 100 * (Close - Lowest Low) / (Highest High - Lowest Low). It represents the current closing price’s position relative to the asset’s recent trading range. %D: A smoothed version of %K, often computed as a 3-period moving average of %K. This smoothing reduces noise and makes trends easier to identify. Trading signals are generated based on the interaction of %K and %D lines. For example: Buy Signal: %K crosses above %D in the oversold region (below 20). Sell Signal: %K crosses below %D in the overbought region (above 80). Hold Signal: %K and %D remain stable without crossing or while hovering in the mid-range (20-80). Understanding these signals is crucial for scalpers, who rely on split-second decisions to enter and exit trades. The stochastic oscillator’s ability to provide actionable insights in fast-moving markets makes it indispensable. Implementing the Stochastic Oscillator in JavaScript Let’s roll up our sleeves and build the stochastic oscillator from scratch in JavaScript. By the end of this section, you’ll have a functional tool that can calculate %K, %D, and generate trading signals. Step 1: Helper Functions for High/Low Calculation To calculate %K, we need the highest high and lowest low over a specified period. Here’s how you can define helper functions: // Calculate the highest high over the last 'n' periods function highestHigh(highs, n) { return Math.max(...highs.slice(0, n)); } // Calculate the lowest low over the last 'n' periods function lowestLow(lows, n) { return Math.min(...lows.slice(0, n)); } Pro Tip: Use JavaScript’s spread operator (...) with Math.max and Math.min for more concise and efficient calculations. Step 2: Calculating %K Now, let’s create a function to calculate the %K value: // Calculate the %K value of the stochastic oscillator function calculateK(close, lows, highs, n) { const lowest = lowestLow(lows, n); const highest = highestHigh(highs, n); if (highest === lowest) return 0; // Avoid division by zero return 100 * ((close[0] - lowest) / (highest - lowest)); } This function takes the most recent closing price, the high and low arrays, and the lookback period (n) as inputs. It ensures the calculation is robust by checking for cases where highest === lowest. Step 3: Smoothing %K to Calculate %D To compute %D, we’ll smooth %K using a simple moving average (SMA): // Calculate the %D value (SMA of %K) function calculateD(kValues, period) { const sum = kValues.slice(0, period).reduce((acc, val) => acc + val, 0); return sum / period; } The kValues array should store the most recent %K values, and the period determines the smoothing length (typically 3). Step 4: Generating Trading Signals With %K and %D computed, we can generate trading signals based on their crossover and thresholds: // Generate trading signals based on %K and %D function generateSignal(k, d) { if (k < 20 && k > d) { return 'BUY'; } else if (k > 80 && k < d) { return 'SELL'; } else { return 'HOLD'; } } Step 5: Putting It All Together Here’s the complete implementation: // Helper functions function highestHigh(highs, n) { return Math.max(...highs.slice(0, n)); } function lowestLow(lows, n) { return Math.min(...lows.slice(0, n)); } // %K calculation function calculateK(close, lows, highs, n) { const lowest = lowestLow(lows, n); const highest = highestHigh(highs, n); if (highest === lowest) return 0; return 100 * ((close[0] - lowest) / (highest - lowest)); } // %D calculation function calculateD(kValues, period) { const sum = kValues.slice(0, period).reduce((acc, val) => acc + val, 0); return sum / period; } // Signal generation function generateSignal(k, d) { if (k < 20 && k > d) { return 'BUY'; } else if (k > 80 && k < d) { return 'SELL'; } else { return 'HOLD'; } } // Example usage const close = [1.2, 1.3, 1.5, 1.1, 1.4]; const highs = [1.4, 1.5, 1.6, 1.3, 1.7]; const lows = [1.1, 1.2, 1.2, 1.0, 1.3]; const n = 3; const k = calculateK(close, lows, highs, n); const d = calculateD([k], 3); const signal = generateSignal(k, d); console.log(`%K: ${k.toFixed(2)}`); console.log(`%D: ${d.toFixed(2)}`); console.log(`Signal: ${signal}`); Optimizing the Stochastic Oscillator Scaling the stochastic oscillator for large datasets or real-time applications requires optimization techniques: Sliding Window: Instead of recalculating the highest high and lowest low for every new data point, use a sliding window approach to update values incrementally. Caching: Cache intermediate calculations to reduce redundant computations, especially for high-frequency trading. Parallel Processing: Leverage JavaScript’s asynchronous capabilities to process data in chunks, minimizing lag. Troubleshooting and Pitfalls Even well-written code can run into issues. Here are some common problems and their solutions: 💡 In practice: After extensive backtesting, I’ve found that the default 14-perio


---
## Bull Call & Bear Put Spreads: JavaScript Calculator

- URL: https://orthogonal.info/javascript-finance-calculates-the-payouts-of-a-bull-call-spread-and-a-bear-put-spread-option-trading-strategies/
- Date: 2022-12-07
- Category: Finance &amp; Trading
- Summary: Build a bull call and bear put spread calculator in JavaScript. Computes max profit, max loss, breakeven points, and visualizes the payoff diagram.

Options Trading Simplified: Building a JavaScript Calculator 📌 TL;DR: Options Trading Simplified: Building a JavaScript Calculator Picture this: you’re eyeing a volatile market, juggling the desire to seize potential opportunities with the need to manage risk. 🎯 Quick Answer: A bull call spread profits when the stock rises moderately: buy a lower-strike call and sell a higher-strike call. A bear put spread profits on moderate decline: buy a higher-strike put and sell a lower-strike put. Max profit is capped at the strike difference minus the net premium paid. I built this exact spread calculator for my own trading workflow. Before entering any vertical spread, I run these numbers to see if the risk/reward actually makes sense — not just the theoretical payoff diagram. Picture this: you’re eyeing a volatile market, juggling the desire to seize potential opportunities with the need to manage risk. Options trading strategies like bull call spreads and bear put spreads can be game-changers for navigating such scenarios. But let’s be honest—understanding the math and mechanics behind them can feel overwhelming. I know because I’ve been there. Years ago, while designing a financial tool for a client, I realized how critical it is to simplify these concepts. What emerged was more than a calculator—it was a gateway to mastering these strategies. I’ll show you how to build a solid bull call and bear put spread calculator using JavaScript. Whether you’re a trader looking for insights or a developer building financial tools, this article will equip you with practical knowledge, real-world code, and essential tips to excel. Understanding Bull Call and Bear Put Spreads First, let’s break down what these strategies are: Bull Call Spread: This is a bullish options strategy. It involves buying a call option at a lower strike price and selling another call option at a higher strike price. The goal? To profit from a moderate rise in the underlying asset’s price, with limited risk. Bear Put Spread: This is a bearish options strategy. It entails buying a put option at a higher strike price and selling another put option at a lower strike price, aiming to benefit from a moderate price decline. Both strategies are categorized as debit spreads because they involve a net premium cost. The trade-off? Capped profits and limited losses, which make them ideal for risk-conscious traders. Pro Tip: Bull call spreads work best in moderately bullish markets, while bear put spreads are suited for moderately bearish conditions. Avoid using them in highly volatile markets where price swings exceed your strike price range. The Mathematics Behind the Strategies At their core, the payouts for these strategies depend on the difference between the strike prices and the underlying asset’s price, minus the net premium paid. Here’s the breakdown: Bull Call Spread Payout: (Price of Underlying - Strike Price of Long Call) - (Price of Underlying - Strike Price of Short Call) - Net Premium Paid Bear Put Spread Payout: (Strike Price of Long Put - Price of Underlying) - (Strike Price of Short Put - Price of Underlying) - Net Premium Paid These formulas might look intimidating, but they’re straightforward to implement programmatically. Let’s dive into the code. Building the JavaScript Calculator 1. Setting Up the Inputs We’ll start by defining the key variables required for the calculations. These include the underlying price, the strike prices of the options, and the net premium paid. // Inputs for the calculator const underlyingPrice = 100; // Current price of the underlying asset const longOptionStrikePrice = 95; // Strike price of the long option const shortOptionStrikePrice = 105; // Strike price of the short option const netPremiumPaid = 3; // Net premium paid for the spread In a real-world scenario, you’d likely collect these inputs through a form in your application. For now, we’ll use hardcoded values to demonstrate the logic. 2. Writing the Calculation Logic Here’s where the magic happens. We’ll create a function to compute the payouts for both strategies: // Function to calculate payouts for bull call and bear put spreads function calculateSpreadPayouts(underlyingPrice, longStrike, shortStrike, netPremium) { // Bull Call Spread Payout const bullCallPayout = Math.max(0, underlyingPrice - longStrike) - Math.max(0, underlyingPrice - shortStrike) - netPremium; // Bear Put Spread Payout const bearPutPayout = Math.max(0, longStrike - underlyingPrice) - Math.max(0, shortStrike - underlyingPrice) - netPremium; return { bullCallPayout, bearPutPayout }; } // Example usage const payouts = calculateSpreadPayouts(underlyingPrice, longOptionStrikePrice, shortOptionStrikePrice, netPremiumPaid); console.log(`Bull Call Spread Payout: $${payouts.bullCallPayout.toFixed(2)}`); console.log(`Bear Put Spread Payout: $${payouts.bearPutPayout.toFixed(2)}`); This function ensures payouts never go below zero, as options cannot have negative intrinsic value. The results are returned as an object for easy access. Pro Tip: Always test your function with edge cases like zero premiums or strike prices close to the underlying price to ensure accuracy. 3. Adding Visualization Numbers alone can be hard to interpret. Adding a visual chart can make your tool much more user-friendly. Here’s how you can use Chart.js to plot payout curves: // Generate data for visualization const prices = Array.from({ length: 21 }, (_, i) => 90 + i); // Range: $90 to $110 const bullCallData = prices.map(price => calculateSpreadPayouts(price, longOptionStrikePrice, shortOptionStrikePrice, netPremiumPaid).bullCallPayout); const bearPutData = prices.map(price => calculateSpreadPayouts(price, longOptionStrikePrice, shortOptionStrikePrice, netPremiumPaid).bearPutPayout); // Example Chart.js setup const ctx = document.getElementById('chart').getContext('2d'); new Chart(ctx, { type: 'line', data: { labels: prices, datasets: [ { label: 'Bull Call Spread', data: bullCallData, borderColor: 'green', fill: false }, { label: 'Bear Put Spread', data: bearPutData, borderColor: 'red', fill: false } ] }, options: { responsive: true, title: { display: true, text: 'Spread Payouts vs Underlying Price' } } }); With this chart, users can instantly see how payouts change across different underlying prices. Common Pitfalls and Troubleshooting Here are some common mistakes to avoid when building your calculator: Incorrect Sign Handling: Ensure you’re subtracting premiums and strike prices in the correct order. Floating-Point Errors: JavaScript’s floating-point arithmetic can cause small inaccuracies. Use libraries like decimal.js for precise calculations. Input Validation: Always validate user inputs to avoid nonsensical values like negative premiums or invalid strike prices. Warning: Never trust user inputs blindly. Validate and sanitize them to prevent injection attacks and ensure calculation integrity. Enhancing Performance If you plan to scale this calculator for high-volume trading scenarios, consider these optimizations: Precompute reusable values to reduce redundancy. Leverage Web Workers for CPU-intensive tasks. Cache results for frequently queried input combinations. Exploring Advanced Features Now that you have the foundation of the calculator, consider adding advanced features: 💡 In practice: When I’m running bull call spreads, I target a strike width that keeps my max loss under 2% of portfolio value. The calculator below is the same logic I use — plug in real bid/ask prices, not just theoretical mid-prices, or you’ll overestimate your edge. Dynamic Inputs: Allow users to select multiple strike prices and premiums for complex strategies. Risk Analysis: Integrate metrics like max gain, max loss, and breakeven points directly into the calculator. Portfolio Integration: Enable users to simulate multiple trades within a portfolio and visualize cumulative outcomes. Quick Summary Bull call and bear put spreads are beginner-friendly strategies for managing risk a


---
## Option Pricing in JS: Forward Implied Volatility

- URL: https://orthogonal.info/javascript-finance-calculate-the-theoretical-price-of-an-option-using-the-forward-implied-volatility/
- Date: 2022-12-06
- Category: Finance &amp; Trading
- Summary: Price options using forward implied volatility in JavaScript. Covers term structure interpolation, vol surface modeling, and Black-Scholes adjustments.

Why Option Pricing Demands Precision and Performance 📌 TL;DR: Why Option Pricing Demands Precision and Performance Picture this: You’re a developer at a fintech startup, and you’ve just launched a new trading platform. The interface looks sleek, and users are flocking to try it out. But almost immediately, the complaints begin pouring in. 🎯 Quick Answer: Forward implied volatility estimates future volatility between two expiration dates using current option prices. Calculate it by extracting implied variance for two expirations and solving for the forward variance between them. This is critical for pricing calendar spreads and term-structure trading strategies accurately. I implemented forward implied volatility calculations in my own trading platform because surface-level IV isn’t enough — you need the term structure to price calendar spreads correctly. Here’s the JavaScript math I actually use. Picture this: You’re a developer at a fintech startup, and you’ve just launched a new trading platform. The interface looks sleek, and users are flocking to try it out. But almost immediately, the complaints begin pouring in. Traders are frustrated because the option prices displayed on your platform don’t line up with the actual market. Some prices are too high, others too low, and no one trusts the system. The root cause? An inaccurate and inefficient option pricing model. Getting option pricing right is one of the most challenging yet critical components of a trading system. It’s not just about crunching numbers—it’s about doing so accurately and in real-time. One key to solving this puzzle is Forward Implied Volatility (FIV), a concept derived from market data that enables more precise option pricing. I’ll walk you through how to implement an option pricing engine in JavaScript using FIV and the Black-Scholes model. Along the way, I’ll share practical tips, working code examples, and common pitfalls to avoid. Forward Implied Volatility: A Deep Dive Forward Implied Volatility (FIV) is a market-derived measure of the expected future volatility of an underlying asset. It plays a central role in pricing options because volatility directly impacts an option’s premium. Traders and developers alike use FIV to standardize comparisons across options with varying strike prices and expiration dates. The formula to calculate FIV is: FIV = sqrt((ln(F/K) + (r + (sigma^2)/2) * T) / T) Where: F: Forward price of the underlying asset K: Option’s strike price r: Risk-free interest rate (e.g., yield on government bonds) sigma: Volatility of the underlying asset T: Time until expiration (in years) FIV ensures that your pricing engine reflects market sentiment about future price fluctuations. For example, if traders expect high volatility in the coming months due to economic uncertainty, FIV will reflect this increased risk. This makes FIV not just a mathematical construct but a dynamic tool for understanding market sentiment. But before we dive into implementation, let’s tackle an often-overlooked aspect: security. Warning: Financial applications are prime targets for attacks. Always validate and sanitize user inputs to prevent invalid or malicious data from corrupting your calculations. Unpacking the Black-Scholes Model The Black-Scholes model is the foundation of modern option pricing. It assumes that the price of the underlying asset follows a geometric Brownian motion with constant volatility and a constant risk-free rate. This model provides closed-form solutions for European-style options, making it both efficient and widely used. The formulas for the theoretical prices of call and put options are: Call = F * N(d1) - K * e^(-r * T) * N(d2) Put = K * e^(-r * T) * N(-d2) - F * N(-d1) Where: N(x): Cumulative normal distribution function d1 and d2 are intermediary calculations, defined as: d1 = (ln(F/K) + (r + (sigma^2)/2) * T) / (sigma * sqrt(T)) d2 = d1 - sigma * sqrt(T) These equations may look intimidating, but they’re straightforward to implement in JavaScript. Let’s see how. Building the Option Pricing Engine: JavaScript Implementation We’ll start by implementing the Black-Scholes formulas for European call and put options. This requires calculating d1, d2, and the cumulative normal distribution function (N(x)). // Function to calculate the price of a European call option function callOptionPrice(F, K, r, sigma, T) { // Calculate d1 and d2 const d1 = (Math.log(F / K) + (r + (sigma ** 2) / 2) * T) / (sigma * Math.sqrt(T)); const d2 = d1 - sigma * Math.sqrt(T); // Calculate the option price using the Black-Scholes formula return F * normalCDF(d1) - K * Math.exp(-r * T) * normalCDF(d2); } // Function to calculate the price of a European put option function putOptionPrice(F, K, r, sigma, T) { // Calculate d1 and d2 const d1 = (Math.log(F / K) + (r + (sigma ** 2) / 2) * T) / (sigma * Math.sqrt(T)); const d2 = d1 - sigma * Math.sqrt(T); // Calculate the option price using the Black-Scholes formula return K * Math.exp(-r * T) * normalCDF(-d2) - F * normalCDF(-d1); } // Cumulative normal distribution function (N(x)) function normalCDF(x) { return 0.5 * (1 + erf(x / Math.sqrt(2))); } // Approximation of the error function (erf) function erf(x) { const a1 = 0.254829592; const a2 = -0.284496736; const a3 = 1.421413741; const a4 = -1.453152027; const a5 = 1.061405429; const p = 0.3275911; const sign = x < 0 ? -1 : 1; x = Math.abs(x); const t = 1 / (1 + p * x); const y = 1 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x); return sign * y; } Here’s a breakdown of what each function does: callOptionPrice: Implements the Black-Scholes formula to compute the theoretical price of a call option. putOptionPrice: Implements the Black-Scholes formula for put options. normalCDF: Approximates the cumulative normal distribution function. erf: Approximates the error function, a key component of normalCDF. Pro Tip: For production-grade applications, consider using robust mathematical libraries like math.js or jstat instead of writing these functions from scratch. These libraries are optimized for performance and precision, and they often come with additional functionalities for advanced financial computations. Optimizing Performance for Real-Time Applications Pricing options in real-time can be computationally expensive, especially when processing large datasets or running on the client side. Here are some strategies to improve performance: Memoization: Cache results of frequently used calculations, such as normalCDF and erf, to avoid redundant computations. Parallelism: Offload calculations to Web Workers to take advantage of multi-threading, particularly for large-scale computations. Precision Management: Use just enough precision for intermediate calculations to avoid unnecessary computational overhead while maintaining accuracy. Batch Processing: If you need to price multiple options simultaneously, consider grouping calculations into batches to reduce the overhead of individual computation calls. Here’s an example of memoizing the normalCDF function: const normalCDFCache = {}; function normalCDF(x) { if (normalCDFCache[x] !== undefined) { return normalCDFCache[x]; } const result = 0.5 * (1 + erf(x / Math.sqrt(2))); normalCDFCache[x] = result; return result; } Warning: Avoid using global caches in multi-threaded environments unless you implement thread-safe mechanisms to manage access. Testing and Debugging Your Implementation Accuracy is crucial in financial applications. Testing your implementation against known benchmarks and edge cases is non-negotiable. Consider the following: Compare your results to those of established financial libraries like QuantLib or NumPy. These libraries are industry standards and offer reliable outputs for validation purposes. Test edge cases, such as zero volatility, very short time to expiration, or extremely high strike prices, to ensure your engine handles unusual scenarios gracefully. Validate your implementation with real ma


---
## Iron Butterfly Options: Profit Probability in JS

- URL: https://orthogonal.info/javascript-finance-calculate-the-profit-probability-of-an-iron-butterfly-option-strategy/
- Date: 2022-12-06
- Category: Finance &amp; Trading
- Summary: Calculate iron butterfly profit probability with JavaScript. Covers strike selection, max profit/loss, breakeven points, and Monte Carlo simulation.

Why Traders Love the Iron Butterfly: A Market Stability Strategy 📌 TL;DR: Why Traders Love the Iron Butterfly: A Market Stability Strategy Picture this: You’re an experienced options trader who has been closely monitoring a stock that seems glued to a narrow trading range. Days turn into weeks, and you’re confident the stock won’t shatter this predictable price corridor. 🎯 Quick Answer: An iron butterfly sells an ATM call and put while buying OTM wings for protection, profiting when the underlying stays near the strike at expiration. Max profit equals the net premium received. Calculate probability of profit by finding the breakeven range where premium collected exceeds potential loss. I use iron butterfly strategies in my own trading system when I spot a stock stuck in a tight range. Here’s the JavaScript math behind calculating profit probability — the same calculations I run before placing a trade. Picture this: You’re an experienced options trader who has been closely monitoring a stock that seems glued to a narrow trading range. Days turn into weeks, and you’re confident the stock won’t shatter this predictable price corridor. What’s your next move? You could seize the opportunity with an iron butterfly strategy—a sophisticated options play that thrives in low-volatility markets. But here’s the challenge: how can you accurately calculate its profit probability? we’ll demystify the iron butterfly strategy, dig into the calculations that underpin its success, and walk through real-world JavaScript code examples to automate those calculations. Whether you’re a trader seeking precision or a developer exploring financial applications, this article will arm you with actionable insights and practical tools. Understanding the Iron Butterfly Strategy The iron butterfly is a neutral options strategy, ideal for range-bound markets. It involves four distinct options contracts: Buy one out-of-the-money (OTM) put: This provides downside protection. Sell one at-the-money (ATM) put: This generates premium income. Sell one ATM call: This creates additional premium income. Buy one OTM call: This caps the potential risk on the upside. The goal is straightforward: profit from the stock price remaining within a specific range at expiration, defined by the breakeven points. Maximum profit is achieved when the stock finishes at the strike price of the sold ATM options, forming the “body” of the butterfly. The strategy leverages the natural decay of options premiums, also known as theta decay, which accelerates as expiration approaches. Pro Tip: The iron butterfly strategy shines in low-volatility environments. Look for stocks with consistently narrow price ranges and low implied volatility in their options. Breaking Down the Components Let’s clarify the key elements you need to understand before diving into calculations: Strike Price: The predetermined price at which the underlying asset can be bought or sold. Upper Breakeven: The highest price at which the strategy breaks even. Lower Breakeven: The lowest price at which the strategy breaks even. Profit Probability: The likelihood of the stock price staying within the breakeven range. These elements collectively define the profitability and risk profile of the iron butterfly strategy. Understanding these concepts is key to executing the strategy effectively. Calculating Breakeven Points: The Foundation Breakeven points are the cornerstone of any options strategy, including the iron butterfly. These points essentially determine the price range within which the strategy remains profitable. Calculating the breakeven points allows traders to understand their risk and reward parameters clearly. The two breakeven points are: Lower Breakeven: The lower boundary of the profit zone. This is calculated as the strike price of the long put minus the net premium received. Upper Breakeven: The upper boundary of the profit zone. This is calculated as the strike price of the long call plus the net premium received. Below is a JavaScript function that automates the calculation of breakeven points: // Function to calculate the breakeven points of an iron butterfly strategy function calculateBreakevens(stockPrice, premiumReceived, longPutStrikePrice, longCallStrikePrice) { const lowerBreakeven = longPutStrikePrice - premiumReceived; const upperBreakeven = longCallStrikePrice + premiumReceived; return { lowerBreakeven, upperBreakeven }; } // Example usage const stockPrice = 100; // Current price of the stock const premiumReceived = 5; // Total premium collected from selling options const longPutStrikePrice = 95; // Strike price of the long put const longCallStrikePrice = 105; // Strike price of the long call const breakevens = calculateBreakevens(stockPrice, premiumReceived, longPutStrikePrice, longCallStrikePrice); console.log(`Lower Breakeven: $${breakevens.lowerBreakeven}`); console.log(`Upper Breakeven: $${breakevens.upperBreakeven}`); This function uses the premium received from selling the ATM options to calculate the breakeven points. These values help traders visualize the range where their strategy is profitable. Warning: Ensure all inputs are accurate, especially strike prices and premium calculations. Misaligned numbers can lead to costly errors and misinterpretations. Calculating Profit Probability with JavaScript Once you’ve established the breakeven points, the next step is to evaluate the probability of profit. This involves determining the likelihood of the stock price staying within the breakeven range. Below is a JavaScript function to calculate profit probability: // Function to calculate the profit probability of an iron butterfly strategy function calculateProfitProbability(stockPrice, lowerBreakeven, upperBreakeven) { if (stockPrice < lowerBreakeven || stockPrice > upperBreakeven) { return 0; // No profit } const range = upperBreakeven - lowerBreakeven; const withinRange = Math.min(stockPrice, upperBreakeven) - Math.max(stockPrice, lowerBreakeven); return (withinRange / range) * 100; // Return as percentage } // Example usage const currentStockPrice = 100; const profitProbability = calculateProfitProbability( currentStockPrice, breakevens.lowerBreakeven, breakevens.upperBreakeven ); console.log(`Profit Probability: ${profitProbability.toFixed(2)}%`); This function evaluates the likelihood of profit based on the current stock price and the breakeven range. It returns the probability as a percentage, giving traders a clear metric to assess their strategy. Common Pitfalls and Troubleshooting Here are some issues you might encounter and how to address them: Incorrect Breakeven Calculations: Double-check your premium inputs and strike prices. Mistakes here can skew the entire analysis. Unrealistic Assumptions: Ensure the stock’s volatility aligns with the strategy’s requirements. High volatility can render an iron butterfly ineffective. Edge Cases: Test scenarios where the stock price touches the breakeven points. These edge cases often reveal calculation bugs. Pro Tip: Use historical stock data to validate your profit probability functions. This ensures your calculations hold up under real-world conditions. Building Real-World Applications With JavaScript, you have the power to create robust tools for options analysis. Imagine integrating the above functions into a trading dashboard where users can input strike prices and premiums to instantly visualize breakeven points and profit probabilities. Here’s an example of how to structure such a tool: <form id="optionsCalculator"> <label for="stockPrice">Stock Price:</label> <input type="number" id="stockPrice" required> <label for="premiumReceived">Premium Received:</label> <input type="number" id="premiumReceived" required> <label for="longPutStrikePrice">Long Put Strike Price:</label> <input type="number" id="longPutStrikePrice" required> <label for="longCallStrikePrice">Long Call Strike Price:</label> <input type="number" id="longCallStrikePrice" required> <button type=


---
## Anker 747 GaNPrime Charger: Multi-Device Review

- URL: https://orthogonal.info/weeks-after-using-anker-747-charger-ganprime/
- Date: 2022-12-06
- Category: Deep Dives
- Summary: In-depth review of the Anker 747 GaNPrime 150W charger. Covers multi-device charging, port allocation, travel-friendliness, and real-world performance.

Why the Anker 747 GaNPrime Charger is a Must-Have 📌 TL;DR: Why the Anker 747 GaNPrime Charger is a Must-Have Picture this: You’re at an airport, juggling a laptop, smartphone, tablet, and wireless earbuds, all battling for a single outlet before your flight. It doesn’t have to be. 🎯 Quick Answer: The Anker 747 GaNPrime 150W charger delivers up to 100W from a single USB-C port and can charge a laptop, phone, tablet, and earbuds simultaneously across 4 ports (3 USB-C, 1 USB-A). GaN technology makes it 38% smaller than traditional 150W chargers. Best multi-device travel charger in its class. I carry this charger every day. As someone who travels with a MacBook, two phones, and an iPad, the Anker 747 GaNPrime replaced three separate chargers in my bag. Here’s my honest review after 6+ months of daily use. Picture this: You’re at an airport, juggling a laptop, smartphone, tablet, and wireless earbuds, all battling for a single outlet before your flight. Sound exhausting? It doesn’t have to be. After weeks of hands-on testing, I can confidently say the Anker 747 GaNPrime Charger is the ultimate solution for multi-device charging. Compact, insanely powerful at 150W, and built with modern GaN (Gallium Nitride) technology, this charger has simplified my tech life in ways I didn’t think possible. In a market flooded with chargers promising speed and efficiency, what sets the Anker 747 apart? It’s a blend of advanced technology, intelligent design, and practical versatility. Let’s dive deep into what makes this charger a standout, from its innovative GaN technology to its real-world performance, and even troubleshooting common issues. What is GaN Technology, and Why Should You Care? The magic behind the Anker 747 is GaN (Gallium Nitride) technology, a revolutionary material changing the way we think about power adapters. Traditional chargers rely on silicon, but GaN is smaller, faster, and more efficient. This isn’t just marketing hype—it’s science that translates into better performance for you. Here’s why GaN is a big improvement: Higher Efficiency: GaN minimizes energy loss during power conversion, allowing your devices to charge faster while generating less heat. Compact Size: GaN components require less space, enabling high-power chargers like the Anker 747 to fit in your palm. Superior Heat Management: GaN dissipates heat more effectively than silicon, keeping the charger cooler even under heavy loads. Pro Tip: GaN chargers are perfect for replacing bulky adapters in your travel bag. They’re lightweight, powerful, and efficient, making them a must-have for road warriors. Real-World Benefits of GaN Technology During my tests, I ran the Anker 747 Charger through its paces. At one point, I had my 16-inch MacBook Pro, iPhone 14 Pro, iPad Pro, and a set of wireless earbuds charging simultaneously. Not only did the charger handle the load easily, but it also stayed cool to the touch—a testament to GaN’s thermal efficiency. And it’s not just about staying cool. Charging speeds are noticeably faster, too. My MacBook Pro hit 50% charge in just 28 minutes, a significant improvement over my old silicon-based charger, which took closer to 45 minutes. For travelers, students, and professionals, this kind of speed and reliability can be a lifesaver. Understanding Why Compact Design Matters One of the standout features of the Anker 747 is its compact design. Measuring just 2.87 x 1.3 x 2.87 inches, this charger is smaller than most traditional laptop chargers yet offers significantly more power. This is a big improvement, especially for those who frequently travel or commute with multiple devices. Instead of lugging around multiple chargers, you can rely on one sleek, lightweight device to do the job. For example, on a recent business trip, I packed only the Anker 747 and a few USB-C cables in my carry-on. This freed up precious space and eliminated the hassle of dealing with tangled cords and bulky adapters. Gone are the days of carrying a separate charger for my laptop, tablet, and phone. The Anker 747 consolidates it all into one compact solution. Exploring USB Power Delivery (USB-PD): The Backbone of Modern Charging The Anker 747 supports USB Power Delivery (USB-PD), a universal standard that intelligently optimizes power output based on the needs of your devices. This ensures each gadget gets the exact amount of power it requires—no more, no less. The result? Faster, safer, and more efficient charging. Understanding USB-PD Power Profiles USB-PD operates across multiple power profiles to accommodate various devices: 5V/3A (15W): Perfect for smartphones, smartwatches, and wireless earbuds. 9V/3A (27W): Ideal for fast-charging smartphones like the latest iPhones or Samsung Galaxy models. 12V/3A (36W): Designed for tablets and mid-sized devices like iPads. 20V/5A (100W): Built for power-hungry laptops, ultrabooks, and gaming devices. Warning: Always use certified USB-C cables rated for high power delivery. Cheap or uncertified cables can overheat, fail, or even damage your devices. The Anker 747 uses USB-PD to allocate power intelligently across its four ports (three USB-C and one USB-A). Whether you’re charging a laptop or just topping off your earbuds, it ensures each device gets best power. Practical Multi-Device Charging Here’s how I typically configure my Anker 747 Charger for daily use: # Device charging setup devices = { "MacBook Pro": {"port": "USB-C1", "power": 85}, # Laptop requires 85W "iPhone": {"port": "USB-C2", "power": 20}, # Smartphone needs 20W "iPad Pro": {"port": "USB-C3", "power": 30}, # Tablet uses 30W "Earbuds": {"port": "USB-A", "power": 10} # Accessory at 10W } total_power = sum(device["power"] for device in devices.values()) if total_power <= 150: print("Charging configuration is valid!") else: print("Power limit exceeded!") With this setup, the total power draw is 145W, leaving a small buffer within the charger’s 150W limit. The dynamic power distribution is another standout feature. If I unplug my laptop, the charger automatically reallocates power to the remaining devices—a level of intelligence I find invaluable. Why Versatility Matters in Everyday Scenarios Beyond travel, the Anker 747 excels in everyday scenarios. For instance, I often work from coffee shops where outlets are precious real estate. With the Anker 747, I can charge my laptop and phone simultaneously without monopolizing multiple outlets. The versatility of having three USB-C ports and one USB-A port means I can power nearly any device I own, from legacy gadgets to the latest tech. Troubleshooting and Avoiding Common Pitfalls Even the best chargers can run into issues. Here are some common problems and how to solve them: Problem 1: Device Charging Slower Than Expected Possible causes and fixes: Ensure you’re using a high-quality USB-C cable rated for the required power level. Verify the port you’re using matches the power needs of your device. Try unplugging and reconnecting the device to reset the power distribution. Problem 2: Charger Overheating While GaN technology minimizes heat, excessive heat can occur due to poor airflow or extreme load. Solutions include: Keep the charger in a well-ventilated space to allow proper cooling. Reduce the number of high-power devices charging simultaneously. Problem 3: Power Allocation Conflicts If the charger’s total power limit is exceeded, some devices may charge slower or not at all. To fix this: Charge high-wattage devices (like laptops) individually when necessary. Use a secondary charger for less critical devices if needed. Final Verdict: Is the Anker 747 Charger Worth It? The Anker 747 GaNPrime Charger has exceeded my expectations in every way. Whether you’re charging a single laptop or juggling multiple devices, its efficiency, compact design, and intelligent power management make it a standout choice. For professionals, students, and frequent travelers, this charger is an investment that pays off in convenience and reliabilit


---
## Iron Condor Profit & Probability with JavaScript

- URL: https://orthogonal.info/javascript-finance-profit-probability-calculator-for-iron-condor/
- Date: 2022-12-06
- Category: Finance &amp; Trading
- Summary: Calculate iron condor profit and probability with JavaScript. Covers strike selection, max gain/loss, breakeven points, and risk-reward visualization.

Picture yourself as an options trader, carefully crafting an iron condor strategy to capitalize on a stable market. The payoff diagram looks promising, and the premiums collected are attractive, but a lingering question remains: what are your actual odds of success? How much risk are you truly taking, and what happens if the market moves unexpectedly? These questions are central to successful trading, and addressing them with data-driven insights can transform your approach from speculative to strategic. I’ll walk you through developing a solid JavaScript tool to calculate the profit or loss of an iron condor at any stock price and estimate the probability of achieving maximum profit or loss. We’ll break down the strategy, explore its components, and build a working function step by step. By the end, you’ll not only understand the mechanics but also have a functional tool to integrate into your trading workflow. Understanding the Iron Condor Strategy 📌 TL;DR: Picture yourself as an options trader, carefully crafting an iron condor strategy to capitalize on a stable market. 🎯 Quick Answer: An iron condor sells an OTM call spread and an OTM put spread simultaneously, profiting when the underlying stays between the short strikes at expiration. Max profit is the total premium collected. Calculate probability of profit using the width between short strikes and implied volatility to estimate the expected price range. I use these exact iron condor calculations in my trading system. Before placing any condor, I run the probability math to verify the expected value is positive — gut feelings don’t survive a large sample size. Here’s the JavaScript implementation. An iron condor is a widely used options trading strategy tailored for low-volatility markets. Its structure includes four options: Sell an out-of-the-money (OTM) call option. Buy a further OTM call option to hedge against large upward moves. Sell an out-of-the-money put option. Buy a further OTM put option to hedge against large downward moves. The beauty of the iron condor lies in its defined risk and reward. The strategy’s maximum profit occurs when the stock price remains between the short call and put strikes at expiration, allowing all options to expire worthless and capturing the net premium. Conversely, the maximum loss is limited to the difference between the strike prices minus the premium collected. Pro Tip: Iron condors thrive in low-volatility environments. Before entering a trade, check the implied volatility of the underlying stock. Higher volatility increases the risk of price swings that could breach your strike prices. Why Iron Condors Are Popular Among Traders Iron condors are popular for several reasons: Defined Risk: Unlike naked options, iron condors cap the maximum potential loss, allowing traders to manage their risk effectively. Flexibility: Traders can adjust strike prices and expiration dates to align with their market outlook and goals. Consistency: In stable markets, iron condors often produce steady returns, making them a favorite for options traders seeking income strategies. Consider this example: imagine the S&P 500 has been trading within a tight range of 4100 to 4200 for weeks. By implementing an iron condor with short strikes at 4100 (put) and 4200 (call), and long strikes at 4050 (put) and 4250 (call), the trader can collect a premium while limiting risk if the index suddenly breaks out. Breaking Down the Problem To create a JavaScript function for this strategy, we need to tackle two core challenges: Calculating the profit or loss at a given stock price. Estimating the probability of achieving maximum profit or loss. Each of these requires a combination of options pricing mechanics and probability theory. Let’s unpack them step by step. 1. Calculating Profit and Loss Profit or loss in an iron condor depends on the stock price relative to the strike prices of the options. Here’s how it plays out: Maximum Profit: Achieved when the stock price stays between the short call and put strikes at expiration. All options expire worthless, and the net premium is kept as profit. Maximum Loss: Occurs when the stock price moves beyond the long call or put strikes. The loss equals the difference between the strike prices minus the premium. Intermediate Scenarios: When the stock price lands between the short and long strikes, the profit or loss is determined by the intrinsic value of the options. For example, if the short call strike is $105, the long call strike is $110, and the stock price is $108, the intrinsic value of the short call option would be $3 ($108 – $105). This value adjusts the profit or loss calculation accordingly. 2. Estimating Probability Probability estimation involves calculating the likelihood of the stock price staying within specific ranges. For this, we use the cumulative distribution function (CDF) of the normal distribution, which requires inputs such as volatility, time to expiration, and the relationship between the stock price and strike prices. Warning: Ensure that your inputs are realistic and accurate. Incorrect data, such as invalid volatility or time values, can lead to erroneous probability calculations and flawed trading decisions. Building the JavaScript Implementation Let’s dive into coding our iron condor calculator. We’ll build the function incrementally, ensuring each piece is functional and tested. Step 1: Setting Up the Function Start with a basic function structure: function ironCondorCalculator(stockPrice, shortCallStrike, longCallStrike, shortPutStrike, longPutStrike, volatility, timeToExpiration) { // Returns profit and probability calculations return { profit: 0, profitProbability: 0, }; } The parameters represent: stockPrice: Current price of the underlying stock. shortCallStrike and longCallStrike: Strike prices for short and long call options. shortPutStrike and longPutStrike: Strike prices for short and long put options. volatility: Implied volatility of the stock. timeToExpiration: Time remaining until expiration (in years). Step 2: Calculating Maximum Profit and Loss Calculate the maximum profit and loss scenarios: function calculateMaxProfitLoss(shortCallStrike, shortPutStrike, longCallStrike, longPutStrike, premiumCollected) { const maxProfit = premiumCollected; const maxLoss = Math.max( longCallStrike - shortCallStrike, shortPutStrike - longPutStrike ) - premiumCollected; return { maxProfit, maxLoss }; } Step 3: Determining Profit at Stock Price Add logic to compute profit based on the stock price: function calculateProfit(stockPrice, shortCallStrike, shortPutStrike, maxProfit, maxLoss) { if (stockPrice < shortPutStrike) { return maxLoss - (shortPutStrike - stockPrice); } else if (stockPrice > shortCallStrike) { return maxLoss - (stockPrice - shortCallStrike); } else { return maxProfit; } } Step 4: Estimating Probability Use the normal distribution to estimate probabilities. Using a library like mathjs simplifies this: const math = require('mathjs'); function calculateProbability(stockPrice, shortCallStrike, volatility, timeToExpiration) { const d1 = (Math.log(stockPrice / shortCallStrike) + (volatility ** 2) * timeToExpiration / 2) / (volatility * Math.sqrt(timeToExpiration)); const d2 = d1 - volatility * Math.sqrt(timeToExpiration); return math.cdf(d1) - math.cdf(d2); } Step 5: Integrating the Final Function Combine all components into the final tool: function ironCondorCalculator(stockPrice, shortCallStrike, longCallStrike, shortPutStrike, longPutStrike, volatility, timeToExpiration, premiumCollected) { const { maxProfit, maxLoss } = calculateMaxProfitLoss(shortCallStrike, shortPutStrike, longCallStrike, longPutStrike, premiumCollected); const profit = calculateProfit(stockPrice, shortCallStrike, shortPutStrike, maxProfit, maxLoss); const profitProbability = calculateProbability(stockPrice, shortCallStrike, volatility, timeToExpiration); return { profit, profitProbability }; } Testing and Troubleshooting Run sample t


---
## Linear Regression: A Beginner-Friendly Guide

- URL: https://orthogonal.info/what-is-a-linear-regression/
- Date: 2022-12-05
- Category: Finance &amp; Trading
- Summary: Learn linear regression from scratch with clear math, Python examples, and real datasets. Perfect for beginners entering machine learning and data science.

Why Linear Regression Still Matters 📌 TL;DR: Why Linear Regression Still Matters Imagine you’re tasked with predicting housing prices for a booming real estate market. Or maybe you’re trying to forecast next quarter’s sales based on advertising spend. 🎯 Quick Answer: Linear regression fits a straight line (y = mx + b) to data by minimizing the sum of squared errors between predicted and actual values. Use R² (coefficient of determination) to measure fit quality—values above 0.7 indicate strong predictive power. It’s the foundation of most financial forecasting models. I use linear regression daily in my financial analysis work — from predicting stock price trends to modeling portfolio risk factors. It’s the foundation of quantitative finance, and understanding it deeply pays dividends. Here’s a practical walkthrough. Imagine you’re tasked with predicting housing prices for a booming real estate market. Or maybe you’re trying to forecast next quarter’s sales based on advertising spend. What’s the first tool you reach for? If you’re like most data analysts, linear regression is likely at the top of your list. Why? Because it’s one of the simplest yet most effective tools for interpreting relationships between variables and making predictions. Linear regression is the bread and butter of statistical modeling and machine learning. Despite its simplicity, it remains a cornerstone for tackling real-world problems, from finance to healthcare. Whether you’re a data science rookie or a seasoned practitioner, mastering linear regression is a skill that pays dividends in countless applications. Let’s dive into the mechanics, applications, and best practices, ensuring you can apply it confidently in your projects. What Exactly is Linear Regression? Linear regression is a statistical technique used to model the relationship between two or more variables. Specifically, it helps us predict the value of a dependent variable (the outcome) based on one or more independent variables (the predictors). This simple yet elegant concept has made linear regression one of the most widely used methods in statistical analysis and predictive modeling. At its core, linear regression assumes a straight-line relationship between the independent and dependent variables. For example, if you’re analyzing how advertising spend affects sales revenue, linear regression helps you quantify the relationship and predict future sales based on advertising budgets. While it may seem basic, this approach has applications ranging from academic research to understanding complex business dynamics. Breaking Down the Components Dependent Variable (Y): The target or outcome we want to predict. For example, this could represent sales revenue, test scores, or stock prices. Independent Variable(s) (X): The input(s) or features used to make the prediction. These could include variables like advertising spend, hours studied, or economic indicators. Regression Line: A straight line that best fits the data, expressed as Y = mX + b, where: m: The slope of the line, indicating how much Y changes for a unit change in X. b: The intercept, representing the value of Y when X equals zero. Linear regression is favored for its interpretability. Unlike more complex models, you can easily understand how each predictor affects the outcome. This simplicity makes it perfect for exploring relationships before moving on to more sophisticated techniques. How Linear Regression Works While the concept is straightforward, implementing linear regression requires several methodical steps. By following these steps, you can ensure your model is both accurate and meaningful: Gather Data: Collect data that includes both predictor(s) and outcome variables. Ensure the dataset is clean and free of errors. Visualize Relationships: Use scatter plots to observe trends and confirm linearity between variables. Visualization can unveil hidden patterns or potential issues like outliers. Fit the Model: Apply a mathematical technique like Ordinary Least Squares (OLS) to find the line of best fit by minimizing residual errors. OLS ensures the total squared difference between observed and predicted values is as small as possible. Evaluate Performance: Use metrics such as R-squared and Mean Squared Error (MSE) to assess how well the model fits the data. A high R-squared value indicates that the model explains a significant portion of the variance. Make Predictions: Use the regression equation to predict outcomes for new input values. This step is particularly useful in forecasting and decision-making processes. Example: Simple Linear Regression in Python Let’s jump straight into a practical example. We’ll predict test scores based on hours studied using Python’s scikit-learn library. First, ensure you have the required libraries installed: pip install numpy matplotlib scikit-learn Here’s the implementation: import numpy as np import matplotlib.pyplot as plt from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score # Dataset: Hours studied vs. Test scores X = np.array([1, 2, 3, 4, 5]).reshape(-1, 1) # Independent variable (Hours studied) Y = np.array([50, 55, 60, 65, 70]) # Dependent variable (Test scores) # Initialize and fit the model model = LinearRegression() model.fit(X, Y) # Make predictions predictions = model.predict(X) # Evaluate the model mse = mean_squared_error(Y, predictions) r2 = r2_score(Y, predictions) # Print results print(f"Slope (m): {model.coef_[0]}") print(f"Intercept (b): {model.intercept_}") print(f"Mean Squared Error: {mse}") print(f"R-squared: {r2}") # Visualize the results plt.scatter(X, Y, color='blue', label='Data Points') plt.plot(X, predictions, color='red', label='Regression Line') plt.xlabel('Hours Studied') plt.ylabel('Test Scores') plt.legend() plt.show() In this example, we trained a simple linear regression model, evaluated its performance, and visualized the regression line alongside the data points. Python’s scikit-learn library makes it easy to implement, even for beginners. Common Challenges and How to Address Them While linear regression is powerful, its simplicity can sometimes lead to pitfalls. To ensure your models are robust, you should be aware of these common challenges and strategies for addressing them: 1. Non-Linearity Linear regression assumes a straight-line relationship between variables. If the relationship is non-linear, the model will underperform. Pro Tip: Visualize your data before applying linear regression. For non-linear patterns, consider polynomial regression or other advanced models like decision trees and neural networks. 2. Multicollinearity When predictor variables are highly correlated with each other, it can distort the model’s coefficients. Warning: Use tools like Variance Inflation Factor (VIF) to detect multicollinearity. If detected, consider removing redundant predictors or using regularization techniques like Lasso regression. 3. Overfitting Overfitting occurs when the model learns noise in the data instead of the actual relationship, leading to poor generalization. Pro Tip: Use cross-validation to test your model on unseen data and avoid overfitting. 4. Outliers Outliers can significantly skew the regression line, leading to biased results. Pro Tip: Identify outliers using box plots or z-scores. Remove or handle them using robust regression techniques. 5. Misinterpreting Results A common mistake is assuming that correlation implies causation. Just because variables are related doesn’t mean one causes the other. Warning: Be cautious in drawing conclusions from regression coefficients. Always consider underlying domain knowledge. Applications of Linear Regression Linear regression is versatile and widely used across industries. Its applications span multiple domains: Marketing: Estimating the effect of advertising spend on sales. Finance: Predicting stock prices based on historical trends. Healthcare: Modeling patient outcomes


---
## C# Performance: 5 Strategies to Optimize Your Code

- URL: https://orthogonal.info/5-simple-checklists-to-improve-c-code-performance/
- Date: 2022-12-05
- Category: C# &amp; .NET
- Summary: Improve C# application performance with 5 proven strategies. Covers async/await, Span, pooling, LINQ optimization, and benchmarking with BenchmarkDotNet.

Imagine this: your C# application is live, users are excited, but suddenly complaints start pouring in. “Why is it so slow?” they ask. The CPU is hitting its limits, memory consumption is climbing, and every click feels like it’s stuck in a tar pit. The frustration is real. I’ve been there—watching a profiler at 2 AM, trying to unravel why a simple loop is hogging resources. Performance bottlenecks can feel like hidden landmines in your code. But here’s the good news: with the right strategies, you can turn your sluggish application into a high-performance marvel. Today, I’ll share five battle-tested techniques to optimize your C# code. These aren’t quick hacks—they’re solid principles every developer should know. Whether you’re managing enterprise software or building your next side project, these strategies will help you write scalable, efficient, and lightning-fast code. 1. Upgrade to the Latest Version of C# and .NET 📌 TL;DR: Imagine this: your C# application is live, users are excited, but suddenly complaints start pouring in. “Why is it so slow?” they ask. The CPU is hitting its limits, memory consumption is climbing, and every click feels like it’s stuck in a tar pit. 🎯 Quick Answer: Optimize C# performance with these 5 strategies: use `Span` and `stackalloc` to avoid heap allocations, prefer `StringBuilder` over string concatenation in loops, cache reflection results, use `ArrayPool` for temporary buffers, and replace LINQ in hot paths with manual loops—LINQ adds 2–5× overhead in tight loops. After profiling .NET services handling 10K+ requests per second at Big Tech, these are the five optimizations that consistently delivered the biggest performance gains. Not theoretical — measured with BenchmarkDotNet and production APM tools. One of the simplest yet most impactful ways to improve performance is to keep your tools updated. Each version of C# and .NET introduces enhancements that can significantly boost your application’s efficiency. For example, .NET 6 brought Just-In-Time (JIT) compiler upgrades and improved garbage collection, while C# 10 introduced interpolated string handlers for faster string manipulation. // Old way (pre-C# 10) string message = "Hello, " + name + "!"; // New way (C# 10): Interpolated string handlers string message = $"Hello, {name}!"; Upgrading isn’t just about new syntax—it’s about Using the underlying optimizations baked into the framework. These improvements can reduce memory allocations, speed up runtime, and improve overall responsiveness. For instance, the introduction of source generators in C# 9 allows for compile-time code generation, which can significantly reduce runtime overhead in certain scenarios. Pro Tip: Always read the release notes for new versions of C# and .NET. They often provide insights into performance enhancements and migration strategies. Warning: Framework upgrades can introduce compatibility issues, especially in legacy projects. Test thoroughly in a staging environment before deployment. Real-World Impact In one project, upgrading from .NET Core 3.1 to .NET 6 reduced average API response times by 30% and slashed memory usage by 20%. No code changes were required—just the upgrade itself. Another example: a team migrating to C# 10 was able to reduce string concatenation overhead by Using interpolated string handlers, Simplifying a critical data processing pipeline. 2. Optimize Algorithms and Data Structures Efficiency in software often boils down to the algorithms and data structures you choose. A poorly chosen data structure can bring your application to its knees, while the right choice can make it soar. But how do you know which one to use? The answer lies in understanding the trade-offs of common data structures and analyzing your specific use case. // Choosing the right data structure var list = new List<int> { 1, 2, 3, 4, 5 }; bool foundInList = list.Contains(3); // O(n) var dictionary = new Dictionary<int, string> { { 1, "One" }, { 2, "Two" } }; bool foundInDictionary = dictionary.ContainsKey(2); // O(1) Likewise, algorithm selection is crucial. For example, if you’re processing sorted data, a binary search can outperform a linear search by orders of magnitude: // Linear search (O(n)) bool LinearSearch(int[] array, int target) { foreach (var item in array) { if (item == target) return true; } return false; } // Binary search (O(log n)) bool BinarySearch(int[] array, int target) { int left = 0, right = array.Length - 1; while (left <= right) { int mid = (left + right) / 2; if (array[mid] == target) return true; if (array[mid] < target) left = mid + 1; else right = mid - 1; } return false; } For a practical example, consider a web application that processes user data. If this data is queried frequently, storing it in a hash-based data structure like a Dictionary or even using a caching layer can dramatically improve performance. Similarly, if you need to frequently sort and search the data, a SortedDictionary or a SortedList might be more appropriate. Pro Tip: Use profiling tools like Visual Studio’s Performance Profiler or JetBrains Rider to detect bottlenecks. They can guide you in choosing better algorithms or data structures. It’s also important to evaluate third-party libraries. Many libraries have already solved common performance challenges in highly optimized ways. For example, libraries like System.Collections.Immutable or third-party options like FastMember can provide dramatic performance boosts for specific use cases. 3. Minimize Redundant Calculations Sometimes, the easiest way to improve performance is to do less work. Redundant calculations—especially inside loops—are silent killers of performance. Consider this common mistake: // Before: Redundant calculation inside loop for (int i = 0; i < items.Count; i++) { var expensiveValue = CalculateExpensiveValue(); Process(items[i], expensiveValue); } // After: Calculate once outside the loop var expensiveValue = CalculateExpensiveValue(); for (int i = 0; i < items.Count; i++) { Process(items[i], expensiveValue); } Lazy evaluation is another powerful technique to defer computations until absolutely necessary. This is particularly useful when calculations are expensive and may not always be needed: // Example: Lazy evaluation Lazy<int> lazyValue = new Lazy<int>(() => ExpensiveComputation()); if (condition) { int value = lazyValue.Value; // Computation happens here } While lazy evaluation can save computation time, it’s also important to assess whether it fits your use case. For example, if you know a value will be used multiple times, it may be better to precompute it and store it in memory rather than lazily evaluating it each time. Warning: Be cautious with lazy evaluation in multithreaded scenarios. Use thread-safe options like Lazy<T>(isThreadSafe: true) to avoid race conditions. 4. Take Advantage of Parallelism and Concurrency Modern processors are multicore, and C# provides tools to use this hardware for better performance. Parallelism and asynchronous programming are two powerful approaches. Consider an application that processes a large dataset. Sequential processing might take hours, but by using Parallel.For, you can divide the workload across multiple threads: // Parallelizing a loop Parallel.For(0, items.Length, i => { Process(items[i]); }); // Asynchronous programming async Task FetchDataAsync() { var data = await httpClient.GetStringAsync("https://example.com"); Console.WriteLine(data); } While parallelism can boost performance, excessive threading can cause contention and overhead. For example, spawning too many threads for small tasks can lead to thread pool exhaustion. Use tools like the Task Parallel Library (TPL) to manage workloads efficiently. Warning: Parallel programming requires thread-safe practices. Use synchronization primitives like lock or SemaphoreSlim to prevent race conditions. 5. Implement Caching and Profiling Caching is one of the most effective ways to improve performance for frequently ac


---
## Python Finance: Option In-the-Money Probability

- URL: https://orthogonal.info/python-finance-calculate-in-the-money-probability-for-an-option/
- Date: 2022-12-05
- Category: Finance &amp; Trading
- Summary: Calculate the probability of an option finishing in the money using Python. Covers Black-Scholes, Monte Carlo simulation, and real market data examples.

Ever Wondered How Likely Your Option Will Finish in the Money? 📌 TL;DR: Ever Wondered How Likely Your Option Will Finish in the Money? Options trading can be exhilarating, but it also comes with its fair share of complexities. One of the most important metrics to understand is the probability that your option will finish in the money (ITM). 🎯 Quick Answer: Use the Black-Scholes cumulative normal distribution function (N(d2) for calls, N(-d2) for puts) in Python with scipy.stats.norm.cdf to calculate the probability an option expires in-the-money based on current price, strike, volatility, time to expiry, and risk-free rate. I run these exact ITM probability calculations in my trading system before entering any options position. Knowing the math behind in-the-money probability changed how I size trades — here’s the Python implementation I use. Options trading can be exhilarating, but it also comes with its fair share of complexities. One of the most important metrics to understand is the probability that your option will finish in the money (ITM). This single calculation can influence your trading strategy, risk management, and overall portfolio performance. As someone who has spent years exploring financial modeling, I know firsthand how daunting these calculations can appear. Fortunately, Python provides an elegant way to compute ITM probabilities using well-established models like Black-Scholes and the Binomial Tree. we’ll dive deep into both methods, share real working code, troubleshoot common pitfalls, and wrap it all up with actionable insights. Pro Tip: Understanding ITM probability doesn’t just help you assess risk—it can also provide insights into implied volatility and market sentiment. Understanding ITM Probability Before jumping into the models, it’s essential to understand what “in the money” means. For a call option, it’s ITM when the underlying asset price is above the strike price. For a put option, it’s ITM when the underlying asset price is below the strike price. The ITM probability is essentially the likelihood that this condition will be true at expiration. Traders use ITM probability to answer critical questions like: Risk Assessment: How likely is it that my option will expire worthless? Profit Potential: What are the chances of my option being profitable at expiration? Portfolio Hedging: Should I buy or sell options to hedge against potential market movements? With these questions in mind, let’s explore two popular methods to calculate ITM probability: Black-Scholes and the Binomial Tree model. Using the Black-Scholes Formula The Black-Scholes model is a cornerstone of modern finance. It assumes that the underlying asset price follows a log-normal distribution and calculates option prices using several key inputs, including volatility and time to expiration. While primarily used for pricing, it can also estimate ITM probability. Here’s how you can implement it in Python: from math import log, sqrt, exp from scipy.stats import norm def black_scholes_itm_probability(option_type, strike_price, underlying_price, volatility, time_to_expiration): # Calculate d1 and d2 d1 = (log(underlying_price / strike_price) + (volatility ** 2 / 2) * time_to_expiration) / (volatility * sqrt(time_to_expiration)) d2 = d1 - volatility * sqrt(time_to_expiration) # Determine in-the-money probability based on option type if option_type.lower() == "call": return norm.cdf(d1) elif option_type.lower() == "put": return norm.cdf(-d2) else: raise ValueError("Invalid option type. Use 'call' or 'put'.") Let’s break this down: d1 and d2 are intermediate variables derived from the Black-Scholes formula. The norm.cdf function calculates the cumulative distribution function (CDF) of the standard normal distribution, which gives us the ITM probability. This function works for European options (exercisable only at expiration). For example: # Inputs option_type = "call" strike_price = 100 underlying_price = 120 volatility = 0.2 # 20% time_to_expiration = 0.5 # 6 months # Calculate ITM probability probability = black_scholes_itm_probability(option_type, strike_price, underlying_price, volatility, time_to_expiration) print(f"In-the-money probability: {probability:.2f}") In this example, the call option has a roughly 70% chance of finishing in the money. Warning: The Black-Scholes model assumes constant volatility and no early exercise. It may not be accurate for American options or assets with high skew. While the Black-Scholes model is efficient, it has limitations. For instance, it assumes constant volatility and risk-free interest rates, which may not reflect real-world conditions. Traders should use this model cautiously and supplement it with other tools if necessary. Binomial Tree Model for Greater Accuracy Unlike Black-Scholes, the binomial model builds a tree of possible asset prices over time, making it more flexible and accurate for options with complex features (like American options). While computationally intensive, it allows for a step-by-step probability calculation. Here’s how to implement it: def construct_binomial_tree(underlying_price, volatility, time_to_expiration, steps): dt = time_to_expiration / steps # Time step u = exp(volatility * sqrt(dt)) # Up factor d = 1 / u # Down factor p = (exp(0.05 * dt) - d) / (u - d) # Risk-neutral probability # Initialize tree tree = [[underlying_price]] for i in range(1, steps + 1): level = [] for j in range(i + 1): price = underlying_price * (u ** j) * (d ** (i - j)) level.append(price) tree.append(level) return tree, p def binomial_itm_probability(option_type, strike_price, underlying_price, volatility, time_to_expiration, steps): tree, p = construct_binomial_tree(underlying_price, volatility, time_to_expiration, steps) itm_probabilities = [] # Calculate ITM probability at each node for level in tree: level_probability = 0 for price in level: if option_type.lower() == "call" and price >= strike_price: level_probability += p elif option_type.lower() == "put" and price <= strike_price: level_probability += p itm_probabilities.append(level_probability / len(level)) # Combine probabilities return sum(itm_probabilities) / len(itm_probabilities) Here’s how you’d use it: # Inputs option_type = "put" strike_price = 100 underlying_price = 120 volatility = 0.2 time_to_expiration = 1 # 1 year steps = 50 # Number of intervals # Calculate ITM probability probability = binomial_itm_probability(option_type, strike_price, underlying_price, volatility, time_to_expiration, steps) print(f"In-the-money probability: {probability:.2f}") With 50 steps, the binomial model provides a refined estimate by considering multiple price paths. Pro Tip: Increase the number of steps for higher accuracy, but be mindful of computational overhead. For most scenarios, 50–100 steps strike a good balance. The binomial model is particularly useful for American options, which allow early exercise. Traders who deal with dividend-paying stocks or assets with variable volatility should consider using this model to account for these complexities. Common Pitfalls and Troubleshooting Calculating ITM probabilities isn’t always straightforward. Here are common issues you might encounter: Incorrect Inputs: Ensure all inputs (volatility, time, etc.) are expressed in the correct units. For example, time should be in years. American vs. European Options: The Black-Scholes model cannot handle early exercise. Use the binomial model for American options. Small Step Size: In the binomial model, using too few steps can lead to inaccurate results. Aim for at least 50 steps for meaningful estimates. Numerical Errors: Floating-point arithmetic can introduce tiny inaccuracies, especially with large numbers of steps. To mitigate these issues, always validate your input data and test your models with different scenarios. For example, try varying the volatility or time-to-expiration to see how the output changes. Advanced Considerations While the models discussed above are po


---
## LINQ Lazy Evaluation: Tips, Pitfalls & Practices

- URL: https://orthogonal.info/laziness-in-linq-select/
- Date: 2022-12-03
- Category: C# &amp; .NET
- Summary: Avoid LINQ lazy evaluation pitfalls in C#. Learn deferred execution, multiple enumeration bugs, materialization strategies, and debugging best practices.

The Mystery of Unexpected Behavior in LINQ 📌 TL;DR: The Mystery of Unexpected Behavior in LINQ Imagine this: you’re on the verge of completing a critical feature for your application, one that processes a list of user IDs to generate reports. You confidently deploy a LINQ query that looks concise and well-structured. 🎯 Quick Answer The Mystery of Unexpected Behavior in LINQ Imagine this: you’re on the verge of completing a critical feature for your application, one that processes a list of user IDs to generate reports. You confidently deploy a LINQ query that looks concise and well-structured. Imagine this: you’re on the verge of completing a critical feature for your application, one that processes a list of user IDs to generate reports. You confidently deploy a LINQ query that looks concise and well-structured. But when you run the code, the results are completely off. A counter you added to debug the process shows zero, and conditional logic based on the data behaves erratically. You’re left wondering, “What just happened?” You’ve encountered one of LINQ’s most powerful yet misunderstood features: lazy evaluation. LINQ queries in .NET don’t execute when you define them; they execute only when you enumerate them. This behavior is at the heart of LINQ’s efficiency, but it can also be a source of confusion if you’re not aware of how it works. we’ll explore the nuances of LINQ’s lazy evaluation, discuss its benefits and pitfalls, and share actionable tips to help you write better LINQ queries. Understanding LINQ’s Lazy Evaluation LINQ (Language Integrated Query) is inherently lazy. When you write a LINQ query, you’re not executing it immediately. Instead, you’re creating a pipeline of operations that will execute only when the data is consumed. This deferred execution allows LINQ to optimize performance, but it can also lead to unexpected results if you’re not careful. Here’s a simple example to illustrate this behavior: int counter = 0; var numbers = new List<int> { 1, 2, 3, 4, 5 }; var query = numbers.Select(n => { counter++; return n * 2; }); // At this point, counter is still 0 because the query hasn't executed Console.WriteLine($"Counter before enumeration: {counter}"); // Enumerate the query to force execution foreach (var result in query) { Console.WriteLine(result); } // Now counter reflects the number of elements processed Console.WriteLine($"Counter after enumeration: {counter}"); When you define the query with Select, no work is done. Only when you enumerate the query (e.g., with a foreach loop) does LINQ process the data, incrementing the counter and generating results. Why LINQ Embraces Laziness Lazy evaluation isn’t a bug—it’s a deliberate design choice. By deferring execution, LINQ achieves several key advantages: Performance: LINQ processes data only when needed, avoiding unnecessary computations. Memory Efficiency: Operations are performed on-the-fly, reducing memory usage for large datasets. Flexibility: You can chain multiple operations together without incurring intermediate costs. For example, consider the following query: var evenNumbers = Enumerable.Range(1, 1000) .Where(n => n % 2 == 0) .Select(n => n * 2); Here, Where filters the even numbers, and Select transforms them. However, neither method does any work until you enumerate evenNumbers. This design ensures that LINQ processes only as much data as necessary. Pro Tip: Chain operations in LINQ to compose powerful queries without additional overhead. Deferred execution ensures that only the final, enumerated results are computed. Common Pitfalls and How to Avoid Them While lazy evaluation is a powerful feature, it can also introduce subtle bugs if you’re not careful. Let’s look at some common pitfalls and how to address them. 1. Debugging Side Effects One of the most common issues arises when you rely on side effects, such as incrementing a counter or logging data, within a LINQ query. As seen earlier, these side effects won’t occur until the query is enumerated. Here’s another example: int counter = 0; var query = Enumerable.Range(1, 5).Select(n => { counter++; return n * 2; }); // At this point, counter is still 0 Console.WriteLine($"Counter: {counter}"); // Force execution var results = query.ToList(); Console.WriteLine($"Counter after forcing execution: {counter}"); To avoid confusion, always ensure that side effects are intentional and that you force execution when necessary using methods like ToList() or ToArray(). 2. Unexpected Multiple Enumerations If you enumerate a LINQ query multiple times, the operations will execute each time, potentially leading to performance issues or incorrect results. Consider this example: var query = Enumerable.Range(1, 5).Select(n => { Console.WriteLine($"Processing {n}"); return n * 2; }); // Enumerate the query twice foreach (var result in query) { } foreach (var result in query) { } Here, the query is processed twice, duplicating the work. To prevent this, materialize the results into a collection if you need to enumerate them multiple times: var results = query.ToList(); foreach (var result in results) { } foreach (var result in results) { } 3. Ignoring Execution Triggers Not all LINQ methods trigger execution. Methods like Where and Select are deferred, while methods like ToList() and Count() are immediate. Be mindful of which methods you use and when. Warning: Forcing execution with methods like ToList() can consume significant memory for large datasets. Use them judiciously. Best Practices for Working with Lazy Evaluation To make the most of LINQ’s lazy evaluation, follow these best practices: Understand when queries execute: Familiarize yourself with which LINQ methods are deferred and which are immediate. Materialize results when necessary: Use ToList() or ToArray() to force execution if you need to reuse the results. Minimize side effects: Avoid relying on side effects within LINQ queries to keep your code predictable. Profile performance: Use tools like dotTrace or Visual Studio’s profiler to measure the impact of your LINQ queries on performance. A Practical Example Let’s combine these tips in a real-world scenario. Suppose you have a list of user IDs and you want to log their processing while generating a report: var userIds = Enumerable.Range(1, 100).ToList(); int logCount = 0; var processedUsers = userIds .Where(id => id % 2 == 0) .Select(id => { logCount++; Console.WriteLine($"Processing User {id}"); return new { UserId = id, IsProcessed = true }; }) .ToList(); Console.WriteLine($"Total users processed: {logCount}"); Here, we use ToList() to force execution, ensuring that all users are processed and logged as intended. Quick Summary LINQ’s lazy evaluation defers execution until the query is enumerated, enabling efficient data processing. Understand the difference between deferred and immediate LINQ methods to avoid unexpected behavior. Force execution with methods like ToList() when relying on side effects or needing reusable results. Avoid multiple enumerations of the same query to prevent redundant computations. Leverage LINQ’s laziness to create efficient, concise, and maintainable code. LINQ’s lazy evaluation is both a powerful tool and a potential pitfall. By understanding how it works and applying best practices, you can use its full potential to write efficient and reliable code. Have you encountered challenges with LINQ’s laziness? Share your experiences and solutions — email [email protected] 🛠 Recommended Resources: Tools and books mentioned in (or relevant to) this article: Beelink EQR6 Mini PC (Ryzen 7 6800U) — Compact powerhouse for Proxmox or TrueNAS ($400-600) Crucial 64GB DDR4 ECC SODIMM Kit — ECC RAM for data integrity ($150-200) APC UPS 1500VA — Battery backup for homelab ($170-200) 📋 Disclosure: Some links are affiliate links. If you purchase through these links, I earn a small commission at no extra cost to you. I only recommend products I have personally used or thoroughly evaluated. 📚 Related Art


---
## Monte Carlo Simulations in JS for Finance

- URL: https://orthogonal.info/javascript-finance-monte-carlo-simulation/
- Date: 2022-12-03
- Category: Finance &amp; Trading
- Summary: Run Monte Carlo simulations in JavaScript for stock price forecasting and portfolio risk analysis. Covers random walks, confidence intervals, and charting.

Unlocking the Power of Randomness in Finance 📌 TL;DR: Unlocking the Power of Randomness in Finance Picture this: you’re tasked with forecasting the future price of a stock in a market that seems to change with the wind. Economic trends, company performance, geopolitical events, and even investor sentiment all play a role. 🎯 Quick Answer: Monte Carlo stock price simulation in JavaScript uses Geometric Brownian Motion: generate thousands of random price paths with drift and volatility, then aggregate outcomes. Running 10,000+ simulations provides a probability distribution of future prices for risk assessment. Picture this: you’re tasked with forecasting the future price of a stock in a market that seems to change with the wind. Economic trends, company performance, geopolitical events, and even investor sentiment all play a role. The problem? These variables are unpredictable. But what if I told you randomness, often seen as chaos, could be your greatest ally in making informed financial predictions? Enter Monte Carlo simulations. Monte Carlo simulations are a cornerstone of quantitative finance, helping professionals estimate risk, forecast returns, and explore a wide range of possible outcomes. By Using randomness and probability distributions, these simulations provide insights that deterministic models simply can’t offer. Whether you’re an aspiring data scientist, a financial analyst, or a developer crafting financial tools, learning Monte Carlo methodologies is a big improvement. we’ll dive deep into implementing Monte Carlo simulations in JavaScript, explore the underlying math, and tackle practical considerations such as optimizing performance and ensuring security. Along the way, I’ll share tips, common pitfalls, and troubleshooting strategies. By the end, you’ll not just know how to code a Monte Carlo simulation—you’ll understand how to use it effectively in real-world applications. Understanding Monte Carlo Simulations Monte Carlo simulations are all about modeling uncertainty. At their core, they run thousands—or even millions—of trials using random inputs, generating data that helps estimate probabilities, risks, and expected values. The technique gets its name from the Monte Carlo Casino in Monaco, reflecting its reliance on randomness. Imagine you’re predicting the future price of a stock. Instead of trying to guess the exact outcome, you use a Monte Carlo simulation to generate thousands of possible scenarios based on random variations in market factors. The aggregated results give you insights into the average price, the range of likely prices, and the probability of extreme events. Monte Carlo simulations aren’t limited to finance; they’re used in physics, engineering, project management, and even game development. But in finance, their ability to model uncertainty makes them indispensable for portfolio optimization, risk management, and forecasting. The Math Behind Monte Carlo Simulations At its core, a Monte Carlo simulation involves sampling random variables from a probability distribution to approximate complex systems. In finance, these random variables often represent factors like returns, volatility, or interest rates. The most common distributions used are: Normal Distribution: Often used to model stock returns, assuming they follow a bell curve with a mean and standard deviation. Uniform Distribution: Generates values evenly distributed across a specified range, useful for simulating equal probabilities. Log-normal Distribution: Models prices that can’t go below zero, commonly applied to simulate stock prices over time. For example, simulating stock prices often involves a formula derived from the geometric Brownian motion (GBM): S(t) = S(0) * exp((μ - σ²/2) * t + σ * W(t)) Here, S(0) is the initial price, μ is the expected return, σ is the volatility, and W(t) is a Wiener process representing randomness over time. Building a Monte Carlo Simulation in JavaScript Let’s roll up our sleeves and dive into the code. We’ll build a Monte Carlo simulation to predict stock prices, taking into account the current price, expected return, and market volatility. Step 1: Defining the Stock Price Model The first step is to create a function that calculates a possible future price of a stock based on random sampling of return rates and volatility. // Define the stock price model function stockPrice(currentPrice, expectedReturn, volatility) { // Generate random variations for return and volatility const randomReturn = (Math.random() - 0.5) * 2 * expectedReturn; const randomVolatility = (Math.random() - 0.5) * 2 * volatility; // Calculate future stock price const futurePrice = currentPrice * (1 + randomReturn + randomVolatility); return futurePrice; } Here, we use Math.random() to generate random values between -1 and 1, simulating variations in return and volatility. The formula calculates the future stock price based on these random factors. Step 2: Running the Simulation Next, we’ll execute this model multiple times to generate a dataset of possible outcomes. This step involves looping through thousands of iterations, each representing a simulation trial. // Run the Monte Carlo simulation const runSimulation = (trials, currentPrice, expectedReturn, volatility) => { const results = []; for (let i = 0; i < trials; i++) { const futurePrice = stockPrice(currentPrice, expectedReturn, volatility); results.push(futurePrice); } return results; }; // Example: 10,000 trials with given parameters const results = runSimulation(10000, 100, 0.05, 0.2); Here, we execute 10,000 trials with a starting price of $100, an expected return of 5%, and a market volatility of 20%. Each result is stored in the results array. Step 3: Analyzing Simulation Results Once we’ve generated the dataset, the next step is to extract meaningful insights, such as the average price, minimum, maximum, and percentiles. // Analyze the simulation results const analyzeResults = (results) => { const averagePrice = results.reduce((sum, price) => sum + price, 0) / results.length; const minPrice = Math.min(...results); const maxPrice = Math.max(...results); return { average: averagePrice, min: minPrice, max: maxPrice, }; }; // Example analysis const analysis = analyzeResults(results); console.log(`Average future price: $${analysis.average.toFixed(2)}`); console.log(`Price range: $${analysis.min.toFixed(2)} - $${analysis.max.toFixed(2)}`); This analysis provides a snapshot of the results, showing the average future price, the range of possible outcomes, and other key metrics. Optimizing Performance in Monte Carlo Simulations Monte Carlo simulations can be computationally demanding, especially when running millions of trials. Here are some strategies to enhance performance: Use Typed Arrays: Replace regular arrays with Float64Array for better memory efficiency and faster computations. Parallel Processing: Use worker_threads in Node.js or Web Workers in the browser to distribute computations across multiple threads. Pre-generate Random Numbers: Create an array of random numbers beforehand to eliminate bottlenecks caused by continuous calls to Math.random(). Common Pitfalls and Troubleshooting Monte Carlo simulations are powerful but not foolproof. Here are common issues to watch for: Non-Cryptographic RNG: JavaScript’s Math.random() isn’t secure for sensitive applications. Use crypto.getRandomValues() when accuracy is critical. Bias in Inputs: Ensure input parameters like expected return and volatility reflect realistic market conditions. Unreasonable assumptions can lead to misleading results. Insufficient Trials: Running too few simulations can yield unreliable results. Aim for at least 10,000 trials, or more depending on your use case. Pro Tip: Visualize your results using charts or graphs. Libraries like Chart.js or D3.js can help you represent data trends effectively. Real-World Applications Monte Carlo simulations are versatile and extend far beyond stock price prediction. Here are a


---
## Ichimoku Cloud in JavaScript: A Trader’s Guide

- URL: https://orthogonal.info/javascript-finance-calculate-ichimoku-value/
- Date: 2022-12-02
- Category: Finance &amp; Trading
- Summary: Build an Ichimoku Cloud indicator in JavaScript for trading. Covers Tenkan, Kijun, Senkou Span calculation, signal interpretation, and chart rendering.

Understanding the Power of the Ichimoku Cloud 📌 TL;DR: Understanding the Power of the Ichimoku Cloud Picture this: You’re analyzing a stock chart, and instead of juggling multiple indicators to gauge trends, momentum, support, and resistance, you have a single tool that does it all. 🎯 Quick Answer: Ichimoku Cloud in JavaScript requires five lines: Tenkan-sen (9-period), Kijun-sen (26-period), Senkou Span A/B (projected 26 periods ahead), and Chikou Span (close shifted 26 back). Price above the cloud signals bullish; below signals bearish. It replaces multiple indicators with one system. Picture this: You’re analyzing a stock chart, and instead of juggling multiple indicators to gauge trends, momentum, support, and resistance, you have a single tool that does it all. Enter the Ichimoku Cloud—a solid trading indicator that offers a complete snapshot of market conditions at a glance. Initially developed by Japanese journalist Goichi Hosoda in the 1930s and released in the 1960s, this tool has become a favorite among traders worldwide. What makes the Ichimoku Cloud stand out is its complete approach to technical analysis. Unlike conventional indicators that focus on isolated aspects like moving averages or RSI, the Ichimoku Cloud combines several elements into one dynamic, visually intuitive system. It’s particularly useful for traders who need to make quick, informed decisions without poring over endless charts. The Ichimoku Cloud is not just a tool for manual analysis. Its methodology can also be applied programmatically, making it ideal for algorithmic trading systems. If you’re a developer building financial applications or exploring algorithmic trading strategies, learning to calculate this indicator programmatically is a big improvement. we’ll dive deep into the Ichimoku Cloud’s components, its JavaScript implementation, and practical tips for integrating it into real-world trading systems. Breaking Down the Components of the Ichimoku Cloud The Ichimoku Cloud is constructed from five key components, each offering unique insights into the market: Tenkan-sen (Conversion Line): The average of the highest high and lowest low over the last 9 periods. It provides an indication of short-term momentum and potential trend reversals. Kijun-sen (Base Line): The average of the highest high and lowest low over the past 26 periods. This serves as a medium-term trend indicator and a dynamic support/resistance level. Senkou Span A (Leading Span A): The average of Tenkan-sen and Kijun-sen, plotted 26 periods into the future. This forms one boundary of the “cloud.” Senkou Span B (Leading Span B): The average of the highest high and lowest low over the past 52 periods, also plotted 26 periods ahead. This is a stronger support/resistance level due to its longer calculation period. Chikou Span (Lagging Span): The current closing price plotted 26 periods backward, providing a historical perspective on price trends. The area between Senkou Span A and Senkou Span B forms the “cloud” or Kumo. When the price is above the cloud, it signals a bullish trend, while a price below the cloud suggests bearish conditions. A price within the cloud often indicates market consolidation or indecision, meaning that neither buyers nor sellers are in control. Traders often use the Ichimoku Cloud not just to identify trends but also to detect potential reversals. For example, a price crossing above the cloud can be a strong bullish signal, while a price falling below the cloud may indicate a bearish trend. Also, the thickness of the cloud can reveal the strength of support or resistance levels. A thicker cloud may serve as a stronger barrier, while a thinner cloud indicates weaker support/resistance. Setting Up a JavaScript Environment for Financial Analysis To calculate the Ichimoku Cloud in JavaScript, you’ll first need a suitable environment. I recommend using Node.js for running JavaScript outside the browser. Also, libraries like axios for HTTP requests and moment.js (or alternatives like dayjs) for date manipulation can simplify your workflow. Pro Tip: Always use libraries designed for handling financial data, such as technicalindicators, if you want pre-built implementations of trading indicators. Start by setting up a Node.js project: mkdir ichimoku-cloud cd ichimoku-cloud npm init -y npm install axios moment The axios library will be used to fetch financial data from external APIs like Alpha Vantage or Yahoo Finance. Sign up for an API key from your chosen provider to access stock price data. Implementing Ichimoku Cloud Calculations in JavaScript Let’s break down the steps to calculate the Ichimoku Cloud. Here’s a JavaScript implementation which assumes you have an array of historical candlestick data, with each entry containing high, low, and close prices: const calculateIchimoku = (data) => { const highValues = data.map(candle => candle.high); const lowValues = data.map(candle => candle.low); const closeValues = data.map(candle => candle.close); const calculateAverage = (values, period) => { const slice = values.slice(-period); return (Math.max(...slice) + Math.min(...slice)) / 2; }; const tenkanSen = calculateAverage(highValues, 9); const kijunSen = calculateAverage(lowValues, 26); const senkouSpanA = (tenkanSen + kijunSen) / 2; const senkouSpanB = calculateAverage(highValues.concat(lowValues), 52); const chikouSpan = closeValues[closeValues.length - 26]; return { tenkanSen, kijunSen, senkouSpanA, senkouSpanB, chikouSpan, }; }; Here’s how each step works: calculateAverage: Computes the midpoint of the highest high and lowest low over a given period. tenkanSen, kijunSen, senkouSpanA, and senkouSpanB: Represent various aspects of trend and support/resistance levels. chikouSpan: Provides a historical comparison of the current price. Warning: Ensure your dataset includes enough data points. For example, calculating Senkou Span B requires at least 52 periods, plus an additional 26 periods for plotting ahead. Fetching Live Stock Data Live data is integral to applying the Ichimoku Cloud in real-world trading. APIs like Alpha Vantage provide historical and live stock prices. Below is an example function to fetch daily stock prices: const axios = require('axios'); const fetchStockData = async (symbol, apiKey) => { const url = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${symbol}&apikey=${apiKey}`; const response = await axios.get(url); const timeSeries = response.data['Time Series (Daily)']; return Object.keys(timeSeries).map(date => ({ date, high: parseFloat(timeSeries[date]['2. high']), low: parseFloat(timeSeries[date]['3. low']), close: parseFloat(timeSeries[date]['4. close']), })); }; Replace symbol with your desired stock ticker (e.g., AAPL) and apiKey with your API key. You can feed the returned data to the calculateIchimoku function for analysis. Building a Trading Decision System Once you’ve calculated Ichimoku values, you can create basic trading logic. Here’s an example: const makeDecision = (ichimoku) => { const { tenkanSen, kijunSen, senkouSpanA, senkouSpanB, chikouSpan } = ichimoku; if (tenkanSen > kijunSen && chikouSpan > senkouSpanA) { return "Buy"; } else if (tenkanSen < kijunSen && chikouSpan < senkouSpanA) { return "Sell"; } else { return "Hold"; } }; (async () => { const data = await fetchStockData('AAPL', 'your_api_key'); const ichimokuValues = calculateIchimoku(data); console.log('Trading Decision:', makeDecision(ichimokuValues)); })(); Expand this logic with additional indicators or conditions for stronger decision-making. For example, you might incorporate RSI or moving averages to confirm trends indicated by the Ichimoku Cloud. Advantages of Using the Ichimoku Cloud Why should traders and developers alike embrace the Ichimoku Cloud? Here are its key advantages: Versatility: The Ichimoku Cloud combines multiple indicators into one, eliminating the need to juggle separate tools for trends, momentum, and support/resistance. Efficiency: Its 


---
## Mastering RSI Calculation in JavaScript for Smarter Trading

- URL: https://orthogonal.info/javascript-finance-calculate-rsi-value/
- Date: 2022-12-01
- Category: Finance &amp; Trading
- Summary: Calculate the Relative Strength Index (RSI) in JavaScript from scratch. Covers the math, smoothing methods, signal interpretation, and trading integration.

Why Relative Strength Index (RSI) Is a Major improvement in Trading 📌 TL;DR: Why Relative Strength Index (RSI) Is a Major improvement in Trading Every trader dreams of perfect timing—buy low, sell high. But how do you actually achieve that? Enter the Relative Strength Index (RSI), one of the most widely used technical indicators in financial analysis. 🎯 Quick Answer: Calculate RSI in JavaScript using a 14-period lookback: compute average gains and losses with Wilder’s smoothing method, then apply RSI = 100 – (100 / (1 + RS)). RSI above 70 indicates overbought conditions; below 30 indicates oversold. Every trader dreams of perfect timing—buy low, sell high. But how do you actually achieve that? Enter the Relative Strength Index (RSI), one of the most widely used technical indicators in financial analysis. RSI acts as a momentum oscillator, giving you a clear signal when an asset is overbought or oversold. It’s not just a tool; it’s a strategic edge in a market full of uncertainty. Here’s the kicker: mastering RSI doesn’t mean just reading its values. To unlock its full potential, you need to understand the math behind it and, if you’re a programmer, know how to implement it. I’ll take you step-by-step through what RSI is, how to calculate it, and how to use JavaScript to integrate it into your financial tools. By the end, you’ll have a solid understanding of RSI, complete with real-world scenarios, implementation, and practical tips. Breaking Down the RSI Formula RSI might seem intimidating at first glance, but it is built on a straightforward formula: RSI = 100 - (100 / (1 + RS)) Here’s what the components mean: RS (Relative Strength): The ratio of average gains to average losses over a specific period. Average Gain: The sum of all positive price changes during the period, divided by the number of periods. Average Loss: The absolute value of all negative price changes during the period, divided by the number of periods. The RSI value ranges between 0 and 100: RSI > 70: The asset is considered overbought, signaling a potential price correction. RSI < 30: The asset is considered oversold, indicating a possible rebound. Steps to Calculate RSI Manually To calculate RSI, follow these steps: Determine the price changes for each period (current price – previous price). Separate the gains (positive changes) from the losses (negative changes). Compute the average gain and average loss over the desired period (e.g., 14 days). Calculate the RS: RS = Average Gain / Average Loss. Plug RS into the RSI formula: RSI = 100 - (100 / (1 + RS)). While this process is simple enough on paper, doing it programmatically is where the real value lies. Let’s dive into the implementation. Implementing RSI in JavaScript JavaScript is an excellent choice for financial analysis, especially if you’re building a web-based trading platform or integrating RSI into an automated system. Here’s how to calculate RSI using JavaScript from scratch: // Function to calculate RSI function calculateRSI(prices, period) { if (prices.length < period + 1) { throw new Error('Not enough data points to calculate RSI'); } const gains = []; const losses = []; // Step 1: Calculate price changes for (let i = 1; i < prices.length; i++) { const change = prices[i] - prices[i - 1]; if (change > 0) { gains.push(change); } else { losses.push(Math.abs(change)); } } // Step 2: Compute average gain and loss for the first period const avgGain = gains.slice(0, period).reduce((acc, val) => acc + val, 0) / period; const avgLoss = losses.slice(0, period).reduce((acc, val) => acc + val, 0) / period; // Step 3: Calculate RS and RSI const rs = avgGain / avgLoss; const rsi = 100 - (100 / (1 + rs)); return parseFloat(rsi.toFixed(2)); // Return RSI rounded to 2 decimal places } // Example Usage const prices = [100, 102, 101, 104, 106, 103, 107, 110]; const period = 5; const rsiValue = calculateRSI(prices, period); console.log(`RSI Value: ${rsiValue}`); In this example, the function calculates the RSI for a given set of prices over a 5-day period. This approach works well for static data, but what about real-time data? Dynamic RSI for Real-Time Data In live trading scenarios, price data constantly updates. Your RSI calculation must adapt efficiently without recalculating everything from scratch. Here’s how to make your RSI calculation dynamic: // Function to calculate dynamic RSI function calculateDynamicRSI(prices, period) { if (prices.length < period + 1) { throw new Error('Not enough data points to calculate RSI'); } let avgGain = 0, avgLoss = 0; // Initialize with the first period for (let i = 1; i <= period; i++) { const change = prices[i] - prices[i - 1]; if (change > 0) { avgGain += change; } else { avgLoss += Math.abs(change); } } avgGain /= period; avgLoss /= period; // Calculate RSI for subsequent data points for (let i = period + 1; i < prices.length; i++) { const change = prices[i] - prices[i - 1]; const gain = change > 0 ? change : 0; const loss = change < 0 ? Math.abs(change) : 0; // Smooth averages using exponential moving average avgGain = ((avgGain * (period - 1)) + gain) / period; avgLoss = ((avgLoss * (period - 1)) + loss) / period; const rs = avgGain / avgLoss; const rsi = 100 - (100 / (1 + rs)); console.log(`RSI at index ${i}: ${rsi.toFixed(2)}`); } } This approach uses a smoothed moving average, making it well-suited for real-time trading strategies. Common Mistakes and How to Avoid Them Here are some common pitfalls to watch for: Insufficient data points: Ensure you have at least period + 1 prices. Zero losses: If there are no losses in the period, RSI will be 100. Handle this edge case carefully. Overreliance on RSI: RSI is not infallible. Use it alongside other indicators for stronger analysis. Pro Tips for Maximizing RSI Effectiveness 🛠 Recommended Resources: Tools and books mentioned in (or relevant to) this article: JavaScript: The Definitive Guide — Complete JS reference ($35-45) You Don’t Know JS Yet (book series) — Deep JavaScript knowledge ($30) Eloquent JavaScript — Modern intro to programming ($25) 📋 Disclosure: Some links are affiliate links. If you purchase through these links, I earn a small commission at no extra cost to you. I only recommend products I have personally used or thoroughly evaluated. 📚 Related Articles Mastering the Stochastic Oscillator in JavaScript for Scalping Mastering Iron Butterfly Options: Profit Probability with JavaScript Mastering Monte Carlo Simulations in JavaScript for Financial Modeling 📊 Free AI Market Intelligence Join Alpha Signal — AI-powered market research delivered daily. Narrative detection, geopolitical risk scoring, sector rotation analysis. Join Free on Telegram → Pro with stock conviction scores: $5/mo { "@context": "https://schema.org", "@type": "TechArticle", "headline": "Mastering RSI Calculation in JavaScript for Smarter Trading", "url": "https://orthogonal.info/javascript-finance-calculate-rsi-value/", "datePublished": "2022-12-01T08:03:28", "dateModified": "2026-03-27T22:13:38", "author": { "@type": "Person", "name": "Max L", "jobTitle": "Security Engineer", "url": "https://orthogonal.info", "knowsAbout": [ "DevSecOps", "Kubernetes Security", "AI Development", "Quantitative Finance", "Cloud Architecture", "Container Security", "Zero Trust", "Infrastructure as Code", "Docker", "Homelab" ] }, "publisher": { "@type": "Organization", "name": "Orthogonal Info", "url": "https://orthogonal.info" }, "description": "Calculate the Relative Strength Index (RSI) in JavaScript from scratch. Covers the math, smoothing methods, signal interpretation, and trading integration.", "wordCount": 1036, "inLanguage": "en-US", "genre": "Finance & Trading", "isAccessibleForFree": true, "speakable": { "@type": "SpeakableSpecification", "cssSelector": [ "h2", ".callout", "p:first-of-type" ] }, "mainEntityOfPage": { "@type": "WebPage", "@id": "https://orthogonal.info/javascript-finance-calculate-rsi-value/" } } { "@context": "https://schema.org", "@type": "FAQPage", 


---
## Mastering SHA-256 Hashing in JavaScript Without Libraries

- URL: https://orthogonal.info/calculate-the-sha-256-hash-of-a-string-in-javascript-without-library/
- Date: 2022-08-06
- Category: JavaScript
- Summary: Compute SHA-256 hashes in JavaScript without external libraries. Learn the Web Crypto API, ArrayBuffer handling, and when to use native browser hashing.

Why Would You Calculate SHA-256 Without Libraries? 📌 TL;DR: Why Would You Calculate SHA-256 Without Libraries? Imagine you’re building a lightweight JavaScript application. You want to implement cryptographic hashing, but pulling in a bulky library like crypto-js or js-sha256 feels like overkill. 🎯 Quick Answer: Implement SHA-256 in JavaScript without libraries using the Web Crypto API: await crypto.subtle.digest(‘SHA-256’, data) returns the hash as an ArrayBuffer. Convert to hex string with Array.from() and toString(16). This is native, fast, and requires zero dependencies. Imagine you’re building a lightweight JavaScript application. You want to implement cryptographic hashing, but pulling in a bulky library like crypto-js or js-sha256 feels like overkill. Or maybe you’re just curious, eager to understand how hashing algorithms actually work by implementing them yourself. Either way, the ability to calculate a SHA-256 hash without relying on external libraries can be a big improvement. Here are some reasons why writing your own implementation might be worth considering: Minimal dependencies: External libraries often add unnecessary bloat, especially for small projects. Deeper understanding: Building a hashing algorithm helps you grasp the underlying concepts of cryptography. Customization: You may need to tweak the hashing process for specific use cases, something that’s hard to do with pre-packaged libraries. I’ll walk you through the process of creating a pure JavaScript implementation of SHA-256. By the end, you’ll not only have a fully functional hashing function but also a solid understanding of how it works under the hood. What Is SHA-256 and Why Does It Matter? SHA-256 (Secure Hash Algorithm 256-bit) is a cornerstone of modern cryptography. It’s a one-way hashing function that takes an input (of any size) and produces a fixed-size, 256-bit (32-byte) hash value. Here’s why SHA-256 is so widely used: Password security: Hashing passwords before storing them prevents unauthorized access. Data integrity: Verifies that files or messages haven’t been tampered with. Blockchain technology: Powers cryptocurrencies by securing transaction data. Its key properties include: Determinism: The same input always produces the same hash. Irreversibility: It’s computationally infeasible to reverse-engineer the input from the hash. Collision resistance: It’s exceedingly unlikely for two different inputs to produce the same hash. These properties make SHA-256 an essential tool for securing sensitive data, authenticating digital signatures, and more. Why Implement SHA-256 Manually? While most developers rely on trusted libraries for cryptographic operations, there are several scenarios where implementing SHA-256 manually might be beneficial: Educational purposes: If you’re a student or enthusiast, implementing a hashing algorithm from scratch is an excellent way to learn about cryptography and understand the mathematical operations involved. Security audits: By writing your own implementation, you can ensure there are no hidden vulnerabilities or backdoors in the hash function. Lightweight applications: For small applications, avoiding dependencies on large libraries can improve performance and reduce complexity. Customization: You might need to modify the algorithm slightly to suit particular requirements, such as using specific padding schemes or integrating it into a proprietary system. However, keep in mind that cryptographic algorithms are notoriously difficult to implement correctly, so unless you have a compelling reason, it’s often safer to rely on well-tested libraries. How the SHA-256 Algorithm Works The SHA-256 algorithm follows a precise sequence of steps. Here’s a simplified roadmap: Initialization: Define initial hash values and constants. Preprocessing: Pad the input to ensure its length is a multiple of 512 bits. Block processing: Divide the padded input into 512-bit chunks and process each block through a series of bitwise and mathematical operations. Output: Combine intermediate results to produce the final 256-bit hash. Let’s break this down into manageable steps to build our implementation. Implementing SHA-256 in JavaScript To implement SHA-256, we’ll divide the code into logical sections: utility functions, constants, block processing, and the main hash function. Let’s get started. Step 1: Utility Functions First, we need helper functions to handle repetitive tasks like rotating bits, padding inputs, and converting strings to byte arrays: function rotateRight(value, amount) { return (value >>> amount) | (value << (32 - amount)); } function toUTF8Bytes(string) { const bytes = []; for (let i = 0; i < string.length; i++) { const codePoint = string.charCodeAt(i); if (codePoint < 0x80) { bytes.push(codePoint); } else if (codePoint < 0x800) { bytes.push(0xc0 | (codePoint >> 6)); bytes.push(0x80 | (codePoint & 0x3f)); } else if (codePoint < 0x10000) { bytes.push(0xe0 | (codePoint >> 12)); bytes.push(0x80 | ((codePoint >> 6) & 0x3f)); bytes.push(0x80 | (codePoint & 0x3f)); } } return bytes; } function padTo512Bits(bytes) { const bitLength = bytes.length * 8; bytes.push(0x80); while ((bytes.length * 8) % 512 !== 448) { bytes.push(0x00); } for (let i = 7; i >= 0; i--) { bytes.push((bitLength >>> (i * 8)) & 0xff); } return bytes; } Pro Tip: Reuse utility functions like rotateRight in other cryptographic algorithms, such as SHA-1 or SHA-512, to save development time. Step 2: Initialization Constants SHA-256 uses a set of predefined constants derived from the fractional parts of the square roots of the first 64 prime numbers. These values are used throughout the algorithm: const INITIAL_HASH = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, ]; const K = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, // ... (remaining 56 constants truncated for brevity) 0xc67178f2 ]; Step 3: Processing 512-Bit Blocks Next, we process each 512-bit block using bitwise operations and modular arithmetic. The intermediate hash values are updated with each iteration: function processBlock(chunk, hash) { const W = new Array(64).fill(0); for (let i = 0; i < 16; i++) { W[i] = (chunk[i * 4] << 24) | (chunk[i * 4 + 1] << 16) | (chunk[i * 4 + 2] << 8) | chunk[i * 4 + 3]; } for (let i = 16; i < 64; i++) { const s0 = rotateRight(W[i - 15], 7) ^ rotateRight(W[i - 15], 18) ^ (W[i - 15] >>> 3); const s1 = rotateRight(W[i - 2], 17) ^ rotateRight(W[i - 2], 19) ^ (W[i - 2] >>> 10); W[i] = (W[i - 16] + s0 + W[i - 7] + s1) >>> 0; } let [a, b, c, d, e, f, g, h] = hash; for (let i = 0; i < 64; i++) { const S1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25); const ch = (e & f) ^ (~e & g); const temp1 = (h + S1 + ch + K[i] + W[i]) >>> 0; const S0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22); const maj = (a & b) ^ (a & c) ^ (b & c); const temp2 = (S0 + maj) >>> 0; h = g; g = f; f = e; e = (d + temp1) >>> 0; d = c; c = b; b = a; a = (temp1 + temp2) >>> 0; } hash[0] = (hash[0] + a) >>> 0; hash[1] = (hash[1] + b) >>> 0; hash[2] = (hash[2] + c) >>> 0; hash[3] = (hash[3] + d) >>> 0; hash[4] = (hash[4] + e) >>> 0; hash[5] = (hash[5] + f) >>> 0; hash[6] = (hash[6] + g) >>> 0; hash[7] = (hash[7] + h) >>> 0; } Step 4: Assembling the Final Function Finally, we combine everything into a single function that calculates the SHA-256 hash: function sha256(input) { const bytes = toUTF8Bytes(input); padTo512Bits(bytes); const hash = [...INITIAL_HASH]; for (let i = 0; i < bytes.length; i += 64) { const chunk = bytes.slice(i, i + 64); processBlock(chunk, hash); } return hash.map(h => h.toString(16).padStart(8, '0')).join(''); } console.log(sha256("Hello, World!")); // Example usage Warning: Always test your implementation with known hashes to ensure correctness. Small mistakes in padding or processing can lead to incorrect results. Quick Summary SHA-256 is a versatile cryptographic hash


---
## Microsoft Graph API with JavaScript: Full Guide

- URL: https://orthogonal.info/make-a-microsoft-graph-call-using-javascript/
- Date: 2022-07-06
- Category: Azure &amp; Cloud
- Summary: Call Microsoft Graph API from JavaScript with authentication, token management, and paginated requests. Full examples for mail, calendar, and user data.

Microsoft Graph API: The Gateway to Microsoft 365 Data 📌 TL;DR: Microsoft Graph API: The Gateway to Microsoft 365 Data Picture this: you’re tasked with building a sleek application that integrates with Microsoft 365 to fetch user emails, calendars, or files from OneDrive. 🎯 Quick Answer Microsoft Graph API: The Gateway to Microsoft 365 Data Picture this: you’re tasked with building a sleek application that integrates with Microsoft 365 to fetch user emails, calendars, or files from OneDrive. Picture this: you’re tasked with building a sleek application that integrates with Microsoft 365 to fetch user emails, calendars, or files from OneDrive. You’ve heard of Microsoft Graph—the unified API endpoint for Microsoft 365—but you’re staring at the documentation, unsure where to begin. If this resonates with you, you’re not alone! Microsoft Graph is an incredibly powerful tool for accessing Microsoft 365 services like Outlook, Teams, SharePoint, and more, all through a single API. However, diving into it can be intimidating for newcomers, especially when it comes to authentication and securely handling API requests. As someone who’s worked extensively with Graph, I’ll guide you through making your first API call using JavaScript, covering crucial security measures, troubleshooting, and tips to optimize your implementation. Why Security Comes First Before jumping into the code, let’s talk about security. Microsoft Graph leverages OAuth 2.0 for authentication, which involves handling access tokens that grant access to user data. Mishandling these tokens can expose sensitive information, making security a top priority. Warning: Never hardcode sensitive credentials like client secrets or access tokens in your source code. Always use environment variables or a secure secrets management service to store them securely. Another vital point is to only request the permissions your app truly needs. Over-permissioning not only poses a security risk but also violates Microsoft’s best practices. For example, if your app only needs to read user emails, avoid requesting broader permissions like full mailbox access. For larger organizations, implementing role-based access control (RBAC) is a key security measure. RBAC ensures that users and applications only have access to the data they truly require. Microsoft Graph API permissions are granular and allow you to provide access to specific resources, such as read-only access to user calendars or write access to OneDrive files. Always follow the principle of least privilege when designing your applications. Step 1: Set Up Your Development Environment The easiest way to interact with Microsoft Graph in JavaScript is through the official @microsoft/microsoft-graph-client library, which simplifies HTTP requests and response handling. You’ll also need an authentication library to handle OAuth 2.0. For this guide, we’ll use @azure/msal-node, Microsoft’s recommended library for Node.js authentication. Start by installing these dependencies: npm install @microsoft/microsoft-graph-client @azure/msal-node Also, if you’re working in a Node.js environment, install isomorphic-fetch to ensure fetch support: npm install isomorphic-fetch These libraries are essential for interacting with Microsoft Graph, and they abstract away much of the complexity involved in making HTTP requests and handling authentication tokens. Once installed, you’re ready to move to the next step. Step 2: Register Your App in Azure Active Directory To authenticate with Microsoft Graph, you’ll need to register your application in Azure Active Directory (AAD). This process generates credentials like a client_id and client_secret, required for API calls. Navigate to the Azure Portal and select “App Registrations.” Click “New Registration” and fill in the details, such as your app name and redirect URI. After registration, note down the Application (client) ID and Directory (tenant) ID. Under “Certificates & Secrets,” create a new client secret. Store it securely, as it won’t be visible again after creation. Once done, configure API permissions. For example, to fetch user profile data, add the User.Read permission under “Microsoft Graph.” It’s worth noting that the API permissions you select during this step determine what your application is allowed to do. For example: Mail.Read: Allows your app to read user emails. Calendars.ReadWrite: Grants access to read and write calendar events. Files.ReadWrite: Provides access to read and write files in OneDrive. Take care to select only the permissions necessary for your application to avoid over-permissioning. Step 3: Authenticate and Acquire an Access Token Authentication is the cornerstone of Microsoft Graph API. Using the msal-node library, you can implement the client credentials flow for server-side applications. Here’s a working example: const msal = require('@azure/msal-node'); // MSAL configuration const config = { auth: { clientId: 'YOUR_APP_CLIENT_ID', authority: 'https://login.microsoftonline.com/YOUR_TENANT_ID', clientSecret: 'YOUR_APP_CLIENT_SECRET', }, }; // Create MSAL client const cca = new msal.ConfidentialClientApplication(config); // Function to get access token async function getAccessToken() { const tokenRequest = { scopes: ['https://graph.microsoft.com/.default'], }; try { const response = await cca.acquireTokenByClientCredential(tokenRequest); return response.accessToken; } catch (error) { console.error('Error acquiring token:', error); throw error; } } module.exports = getAccessToken; This function retrieves an access token using the client credentials flow, ideal for server-side apps like APIs or background services. Pro Tip: If you’re building a front-end app, use the Authorization Code flow instead. This flow is better suited for interactive client-side applications. In the case of front-end JavaScript apps, you can use the @azure/msal-browser library to implement the Authorization Code flow, which involves redirecting users to Microsoft’s login page. Step 4: Make Your First Microsoft Graph API Call With your access token in hand, it’s time to interact with Microsoft Graph. Let’s start by fetching the authenticated user’s profile using the /me endpoint: const { Client } = require('@microsoft/microsoft-graph-client'); require('isomorphic-fetch'); // Support for fetch in Node.js async function getUserProfile(accessToken) { const client = Client.init({ authProvider: (done) => { done(null, accessToken); }, }); try { const user = await client.api('/me').get(); console.log('User profile:', user); } catch (error) { console.error('Error fetching user profile:', error); } } // Example usage (async () => { const getAccessToken = require('./getAccessToken'); // Import token function const accessToken = await getAccessToken(); await getUserProfile(accessToken); })(); This example initializes the Microsoft Graph client and uses the /me endpoint to fetch user profile data. Replace the placeholder values with your app credentials. Step 5: Debugging and Common Pitfalls Errors are inevitable when working with APIs. Microsoft Graph uses standard HTTP status codes to indicate issues. Here are common ones you may encounter: 401 Unauthorized: Ensure your access token is valid and hasn’t expired. 403 Forbidden: Verify the permissions (scopes) granted to your app. 429 Too Many Requests: You’ve hit a rate limit. Implement retry logic with exponential backoff. To simplify debugging, enable logging in the Graph client: const client = Client.init({ authProvider: (done) => { done(null, accessToken); }, debugLogging: true, // Enable debug logging }); Step 6: Advanced Techniques for Scaling As you grow your implementation, efficiency becomes key. Here are some advanced tips: Batching: Combine multiple API calls into a single request using the /$batch endpoint to reduce network overhead. Pagination: Many endpoints return paginated data. Use the @odata.nextLink property to fetch subsequent pages. Throttling: Avoid rate limits by i


---
## Launch Edge with Specific Profiles via Command Line

- URL: https://orthogonal.info/how-to-start-edge-browser-with-work-profile-in-command-line/
- Date: 2022-05-10
- Category: Quick Wins
- Summary: Launch Microsoft Edge with a specific browser profile from the command line. Useful for automation, testing, and managing multi-account workflows.

Kicking Off Your Day Without Profile Mishaps 📌 TL;DR: Kicking Off Your Day Without Profile Mishaps Picture this: It’s a workday morning, and you sit down at your desk, ready to dive into emails, reports, and pressing tasks. 🎯 Quick Answer: Launch Microsoft Edge with a specific profile using: msedge.exe –profile-directory=”Profile 1″ from the command line. Find profile folder names in edge://version under “Profile Path.” Replace “Profile 1” with “Default” for the primary profile. Picture this: It’s a workday morning, and you sit down at your desk, ready to dive into emails, reports, and pressing tasks. You fire up your automation tool, press a button, and wait for Outlook to launch in Microsoft Edge, expecting your work profile to load. But instead of your professional workspace, your personal profile pops up, showing forgotten shopping carts, social media notifications, and last night’s memes. Sound familiar? If you manage multiple profiles in Edge, this scenario is all too common. Thankfully, there’s an easy fix to ensure your browser behaves exactly how you need it to, every single time. Why Profile Management in Microsoft Edge Matters Microsoft Edge has gained significant traction in recent years due to its speed, integration with Windows, and robust profile management capabilities. The ability to maintain separate profiles is a big improvement for those juggling multiple accounts, whether for work, personal use, or other projects. Each profile keeps its own browsing history, extensions, saved passwords, and cookies, creating a clean separation between your personas. For professionals, this separation is invaluable. Imagine working on confidential documents in one profile while casually browsing news articles in another. No more worrying about mixing tabs or accidentally saving sensitive credentials in the wrong account. However, despite these advantages, Edge’s default behavior can sometimes be a headache—especially when launching the browser through command-line tools or automation software. By default, Edge often opens your primary profile, which is usually your personal account. This can cause frustration and disrupt workflows when you need quick access to your work profile. The Command Line Solution to Launch Specific Profiles After experimenting with various techniques and scouring Edge’s documentation, I discovered the secret to launching Edge with a specific profile using command-line options. By Using the --profile-directory flag, you can specify which profile Edge should use upon launch. Here’s a basic example: start msedge --profile-directory="Profile 1" https://outlook.office.com/owa/ Let’s break down the components of this command: start msedge: This command launches Microsoft Edge from the command line. It’s the foundation for opening Edge in this method. --profile-directory="Profile 1": Specifies which profile Edge should use. “Profile 1” typically refers to your first added profile, but the exact name depends on your setup. https://outlook.office.com/owa/: Opens Outlook Web Access directly within the selected profile, saving you time and effort. Pro Tip: Unsure of your profile directory name? Navigate to %LOCALAPPDATA%\Microsoft\Edge\User Data on your computer. You’ll find folders labeled Profile 1, Profile 2, and so on. Compare these folders to your profiles in Edge to identify the one you wish to use. Expanding Automation: Batch Files and Beyond If you frequently switch between profiles or automate browser launches, embedding this command into batch files or automation tools can save you valuable time. Here’s an example of a simple batch file: @echo off start msedge --profile-directory="Profile 1" https://outlook.office.com/owa/ start msedge --profile-directory="Profile 2" https://github.com/ exit Save the script with a .bat extension, and double-click it to launch multiple Edge instances with their respective profiles and URLs. This setup is particularly useful for developers, remote workers, or anyone managing multiple accounts or workspaces. Using Stream Deck for Profile Automation For users looking to simplify this process even further, tools like Elgato Stream Deck provide an elegant solution. Stream Deck allows you to create customizable shortcuts for launching applications and executing commands. Here’s how to set up Edge with specific profiles on Stream Deck: Open the Stream Deck software and add a new action to launch an application. Set the application path to "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe". Input the arguments: --profile-directory="Profile 1" https://outlook.office.com/owa/. Save the configuration and test it by pressing the assigned button. With Stream Deck, you can create dedicated shortcuts for launching Edge with various profiles and URLs, further enhancing your workflow efficiency. Common Pitfalls and How to Avoid Them While the command-line approach is straightforward, a few common pitfalls can arise. Here’s how to troubleshoot and prevent them: Incorrect Profile Directory: Using the wrong profile name will cause Edge to default to your primary profile. Always double-check the profile folder names in %LOCALAPPDATA%\Microsoft\Edge\User Data. Spaces in Profile Names: If the profile directory contains spaces (e.g., “Work Profile”), enclose the name in double quotes, like --profile-directory="Work Profile". Executable Path Issues: On some systems, the msedge command may not be recognized. Use the full path to Edge’s executable, such as "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe". Complex URLs: Some URLs with query strings or parameters may not parse correctly. Wrap the URL in double quotes if necessary. Warning: Be cautious when using automated scripts, particularly if they handle sensitive URLs or credentials. Store scripts securely and limit access to authorized users only. Advanced Use Cases: Multi-Profile Launches For power users managing multiple profiles simultaneously, launching Edge instances for each profile with unique URLs can boost productivity. Here’s an example: start msedge --profile-directory="Profile 2" https://calendar.google.com/ start msedge --profile-directory="Profile 3" https://teams.microsoft.com/ start msedge --profile-directory="Profile 1" https://outlook.office.com/owa/ This configuration is perfect for professionals who need immediate access to tools like email, team collaboration platforms, and project dashboards across different profiles. Troubleshooting Profile Launch Errors If Edge refuses to launch with your specified profile, try these troubleshooting steps: Verify Installation Path: Ensure the Edge executable path matches your system’s installation directory. Update Edge: Always use the latest version of Microsoft Edge, as command-line flag behavior may vary across versions. Organizational Policies: Some IT policies disable command-line flags for browsers. Contact your administrator if you’re unable to use this feature. URL Simplicity: Test the command with a basic URL (e.g., https://google.com) to isolate issues related to complex URLs. Quick Summary Use the --profile-directory flag to launch Edge with specific profiles via the command line. Embed commands into batch files or automation tools for seamless workflow integration. Double-check profile directory names and paths to avoid common errors. Leverage Edge’s profile management to maintain separate browsing environments for work and personal use. Secure scripts and validate URLs to prevent mishaps. With these methods, you’ll turn Microsoft Edge into a powerful tool for productivity and organization, ensuring your profiles load as intended every time. Whether you’re a developer, a remote worker, or simply someone who values efficiency, these strategies will transform how you use your browser. Happy browsing! 🛠 Recommended Resources: Tools and books mentioned in (or relevant to) this article: LG 27UN850-W 4K Monitor — 27-inch 4K USB-C monitor for coding ($350-450) Keychron K8 TK


---
## Restore Full Right-Click Menu in Windows 11

- URL: https://orthogonal.info/how-to-always-show-full-right-click-menu-in-windows-11/
- Date: 2022-05-06
- Category: Quick Wins
- Summary: Bring back the classic right-click context menu in Windows 11 with a simple registry tweak. No third-party software needed — works on all editions.

Why You Need the Full Context Menu in Windows 11 📌 TL;DR: Why You Need the Full Context Menu in Windows 11 Imagine you’re in the middle of a development sprint, right-clicking to perform a quick action—say, editing a file or running a script. But instead of seeing all the options you’re used to, you’re greeted with a minimalist context menu. 🎯 Quick Answer Why You Need the Full Context Menu in Windows 11 Imagine you’re in the middle of a development sprint, right-clicking to perform a quick action—say, editing a file or running a script. But instead of seeing all the options you’re used to, you’re greeted with a minimalist context menu. Imagine you’re in the middle of a development sprint, right-clicking to perform a quick action—say, editing a file or running a script. But instead of seeing all the options you’re used to, you’re greeted with a minimalist context menu. The option you need is buried under “Show more options.” Sound familiar? If you’re working on Windows 11, this is your new reality. While Microsoft aimed to simplify the context menu for casual users, this change can be a productivity killer for developers, IT professionals, and power users. Thankfully, you don’t have to settle for this. With a simple tweak, you can restore the classic, full right-click menu and reclaim your workflow efficiency. I’ll show you how to make it happen with detailed steps, code examples, troubleshooting advice, and additional tips to help you maximize your productivity. Understanding Microsoft’s Context Menu Changes Microsoft introduced the streamlined context menu in Windows 11 as part of its design overhaul. The idea was to offer a cleaner, less cluttered user experience. By grouping secondary options under “Show more options,” Microsoft hoped to make common tasks faster for everyday users. However, this design choice doesn’t align with the needs of power users who rely on the full context menu for tasks like: Editing files with specific programs like Notepad++, Visual Studio Code, or Sublime Text Accessing version control tools like Git or SVN Renaming, copying, or deleting files quickly without extra clicks Performing advanced file operations such as compression, encryption, or file sharing For casual users, the simplified context menu might seem helpful—but for professionals juggling dozens of files and processes daily, it creates unnecessary friction. Tasks that once took one click now require two or more. This may not seem like a big deal initially, but over time, the extra clicks add up, slowing down your workflow. Fortunately, there’s a way to bypass this frustration entirely. With a simple registry modification, you can restore the full context menu and enjoy the functionality you’ve come to rely on. Let’s dive into the process step-by-step. How to Restore the Full Context Menu To bring back the classic right-click menu in Windows 11, you’ll need to make a small change to the Windows Registry. Don’t worry; it’s straightforward and safe as long as you follow the steps carefully. If you’re unfamiliar with registry editing, don’t worry—this guide will walk you through everything. Step 1: Open Command Prompt as Administrator Before making changes to the registry, you’ll need administrative privileges to ensure the tweaks are applied correctly: Press Win + S, type “cmd” into the search bar, and when Command Prompt appears, right-click it and choose Run as administrator. Confirm the User Account Control (UAC) prompt if it appears. Once opened, Command Prompt will allow you to execute the necessary commands for modifying the registry. Step 2: Add the Registry Key To restore the classic context menu, you need to add a specific registry key. This key tells Windows to revert to the old behavior: reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve Here’s what each part of the command does: reg add: Adds a new registry key or value. "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}": Specifies the location in the registry where the key will be added. /f: Forces the addition of the key without confirmation. /ve: Specifies that the value should be empty. After running this command, the necessary registry key will be added, instructing Windows to use the full context menu. Step 3: Restart Windows Explorer For the changes to take effect, you need to restart Windows Explorer. You can do this directly from the Command Prompt. Run the following commands individually: taskkill /f /im explorer.exe start explorer.exe The first command forcefully stops Windows Explorer, while the second one starts it again. Once restarted, your classic context menu should be restored. To confirm, right-click on any file or folder and check if the full menu appears. Troubleshooting and Common Pitfalls Although the process is generally hassle-free, you might encounter a few issues along the way. Here’s how to address them: 1. Registry Edit Doesn’t Work If the classic context menu isn’t restored after following the steps: Double-check the registry command you entered. Even a small typo can cause the tweak to fail. Ensure you ran Command Prompt as an administrator. Without admin privileges, the registry edit won’t apply. 2. Windows Explorer Fails to Restart If Explorer doesn’t restart properly after running the restart commands, you can restart it manually: Press Ctrl + Shift + Esc to open Task Manager. Under the Processes tab, locate Windows Explorer. Right-click it and select Restart. 3. Changes Revert After Windows Update Some major Windows updates can reset registry modifications. If your context menu reverts to the default minimalist style after an update, simply repeat the steps above to reapply the tweak. Warning: Be cautious when editing the Windows Registry. Incorrect changes can cause system instability. Always double-check commands and back up your registry before making any tweaks. Advanced Options for Customization Beyond restoring the full context menu, there are other ways to optimize it for your workflow. Here are a few advanced options: 1. Using Third-Party Tools Tools like ShellExView allow you to disable or enable individual context menu items. This is particularly useful for removing rarely-used options, making the menu less cluttered. 2. Registry Backups Before making any major changes to the registry, consider exporting the specific key you’re editing. This creates a .reg file that you can use to restore the original settings if something goes wrong. Pro Tip: To export a registry key, open Registry Editor (Win + R, type “regedit”), navigate to the key, right-click it, and choose Export. Reverting to the Default Context Menu If you decide you prefer the streamlined menu or need to undo the changes for any reason, reverting to the default settings is simple: Open Command Prompt as Administrator. Run the following commands: reg delete "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}" /f taskkill /f /im explorer.exe start explorer.exe This command deletes the registry key and restarts Explorer, restoring the default context menu behavior. Quick Summary Windows 11’s minimalist context menu may look sleek but can slow down power users. Restoring the full right-click menu is as simple as adding a registry key and restarting Explorer. Always use administrative privileges and double-check commands when editing the registry. If changes revert after a Windows update, repeat the steps to reapply the tweak. For advanced customization, consider using tools like ShellExView to manage context menu entries. With these steps, you can take back control of your right-click menu and simplify your workflow in Windows 11. Whether you’re a developer, IT professional, or just someone who values efficiency, this tweak can dramatically improve your experience. Give it a try, and let me know how it works for you! 🛠 Recommended Resources: Tools and books mentioned in (or relevant to) this article: Options, Future


---
## Mastering Azure CLI: Complete Guide to VM Management

- URL: https://orthogonal.info/how-to-use-az-command-to-control-vms/
- Date: 2022-04-13
- Category: Azure &amp; Cloud
- Summary: Master Azure CLI for virtual machine management. Covers VM creation, start/stop, resize, disk management, and automation scripts for cloud operations.

Why Azure CLI is a Major improvement for VM Management 📌 TL;DR: Why Azure CLI is a Major improvement for VM Management Imagine this scenario: your team is facing a critical deadline, and a cloud-based virtual machine (VM) needs to be deployed and configured instantly. 🎯 Quick Answer: Manage Azure VMs with az CLI using: az vm create to provision, az vm start/stop/restart for power control, az vm list for inventory, and az vm resize to change SKU. Add –no-wait for async operations on large fleets. Imagine this scenario: your team is facing a critical deadline, and a cloud-based virtual machine (VM) needs to be deployed and configured instantly. Clicking through the Azure portal is one option, but it’s time-consuming and prone to human error. Real professionals use the az CLI—not just because it’s faster, but because it offers precision, automation, and unparalleled control over your Azure resources. I’ll walk you through the essentials of managing Azure VMs using the az CLI. From deploying your first VM to troubleshooting common issues, you’ll learn actionable techniques to save time and avoid costly mistakes. Whether you’re a beginner or an advanced user, this guide will enhance your cloud management skills. Benefits of Using Azure CLI for VM Management Before diving into the specifics, let’s discuss why the Azure CLI is considered a big improvement for managing Azure virtual machines. Speed and Efficiency: CLI commands are typically faster than navigating through the Azure portal. With just a few lines of code, you can accomplish tasks that might take minutes in the GUI. Automation: Azure CLI commands can be integrated into scripts, enabling you to automate repetitive tasks like VM creation, scaling, and monitoring. Precision: CLI commands allow you to specify exact configurations, reducing the risk of misconfigurations that could occur when using a graphical interface. Repeatability: Because commands can be saved and reused, Azure CLI ensures consistency when deploying resources across multiple environments. Cross-Platform Support: Azure CLI runs on Windows, macOS, and Linux, making it accessible to a wide range of users and development environments. Script Integration: The CLI’s output can be easily parsed and used in other scripts, enabling advanced workflows and integration with third-party tools. Now that you understand the benefits, let’s get started with a hands-on guide to managing Azure VMs with the CLI. Step 1: Setting Up a Resource Group Every Azure resource belongs to a resource group, which acts as a logical container. Starting with a well-organized resource group is critical for managing and organizing your cloud infrastructure effectively. Think of resource groups as folders that hold all the components of a project, such as virtual machines, storage accounts, and networking resources. az group create --name MyResourceGroup --location eastus This command creates a resource group named MyResourceGroup in the East US region. Pro Tip: Always choose a region close to your target user base to minimize latency. Azure has data centers worldwide, so select the location strategically. Warning: Resource group names must be unique within your Azure subscription. Attempting to reuse an existing name will result in an error. Resource groups are also useful for managing costs. By grouping related resources together, you can easily track and analyze costs for a specific project or workload. Step 2: Deploying a Virtual Machine With your resource group in place, it’s time to launch a virtual machine. For this example, we’ll create an Ubuntu 20.04 LTS instance—a solid choice for most workloads. The Azure CLI simplifies the deployment process, allowing you to specify all the necessary parameters in one command. az vm create \ --resource-group MyResourceGroup \ --name MyUbuntuVM \ --image UbuntuLTS \ --admin-username azureuser \ --generate-ssh-keys This command performs the following tasks: Creates a VM named MyUbuntuVM within the specified resource group. Specifies an Ubuntu LTS image as the operating system. Generates SSH keys automatically, saving you from the hassle of managing passwords. The simplicity of this command masks its power. Behind the scenes, Azure CLI provisions the VM, configures networking, and sets up storage, all in a matter of minutes. Pro Tip: Use descriptive resource names (e.g., WebServer01) to make your infrastructure easier to understand and manage. Warning: Failing to specify --admin-username may result in unexpected defaults that could lock you out of your VM. Always set it explicitly. Step 3: Managing the VM Lifecycle Virtual machines aren’t static resources. To optimize costs and maintain reliability, you’ll need to manage their lifecycle actively. Common VM lifecycle operations include starting, stopping, redeploying, resizing, and deallocating. Here are some common commands: # Start the VM az vm start --resource-group MyResourceGroup --name MyUbuntuVM # Stop the VM (does not release resources) az vm stop --resource-group MyResourceGroup --name MyUbuntuVM # Deallocate the VM (releases compute resources and reduces costs) az vm deallocate --resource-group MyResourceGroup --name MyUbuntuVM # Redeploy the VM (useful for resolving networking issues) az vm redeploy --resource-group MyResourceGroup --name MyUbuntuVM Pro Tip: Use az vm deallocate instead of az vm stop to stop billing for compute resources when the VM is idle. Warning: Redeploying a VM resets its network interface. Plan carefully to avoid unexpected downtime. Azure CLI also allows you to resize your VM to match changing workload requirements. For example: az vm resize \ --resource-group MyResourceGroup \ --name MyUbuntuVM \ --size Standard_DS3_v2 The above command changes the VM size to a Standard_DS3_v2 instance. Always verify the new size’s compatibility with your region and workload requirements before resizing. Step 4: Retrieving the VM’s Public IP Address To access your VM, you’ll need its public IP address. The az vm show command makes this simple. az vm show \ --resource-group MyResourceGroup \ --name MyUbuntuVM \ --show-details \ --query publicIps \ --output tsv This command extracts the VM’s public IP address in a tab-separated format, perfect for use in scripts or command chaining. Pro Tip: Include the --show-details flag to get additional instance metadata alongside the public IP address. Warning: If you don’t see a public IP address, it might not be enabled for the network interface. Check your network configuration or assign a public IP manually. Step 5: Accessing the VM via SSH Once you have the public IP address, connecting to your VM via SSH is straightforward. Replace <VM_PUBLIC_IP> with the actual IP address you retrieved earlier. ssh azureuser@<VM_PUBLIC_IP> Want to run commands remotely? For example, to check the VM’s uptime: ssh azureuser@<VM_PUBLIC_IP> "uptime" Pro Tip: Automate SSH access by adding your public key to the ~/.ssh/authorized_keys file on the VM. Warning: Ensure your local SSH key matches the VM’s key. Mismatched keys will result in an authentication failure. Step 6: Monitoring and Troubleshooting Efficient VM management isn’t just about deployment—it’s also about monitoring and troubleshooting. The Azure CLI offers several commands to help you diagnose issues and maintain best performance. View VM Status az vm get-instance-view \ --resource-group MyResourceGroup \ --name MyUbuntuVM \ --query instanceView.statuses This command provides detailed information about the VM’s current state, including power status and provisioning state. Check Resource Usage az monitor metrics list \ --resource /subscriptions/<subscription-id>/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/MyUbuntuVM \ --metric "Percentage CPU" \ --interval PT1H Replace <subscription-id> with your Azure subscription ID. This command retrieves CPU usage metrics, helping you identify performance bottlenecks. Troubleshooting Networking Issues


---
## Install Python pip on CentOS Core Enterprise

- URL: https://orthogonal.info/how-to-install-python-pip-on-centoos-core-enterprise/
- Date: 2022-04-08
- Category: Python
- Summary: Step-by-step guide to install Python pip on CentOS Core Enterprise. Covers EPEL setup, Python version management, virtualenv, and troubleshooting tips.

Why Installing pip on CentOS Core Enterprise Can Be Tricky 📌 TL;DR: Why Installing pip on CentOS Core Enterprise Can Be Tricky Picture this: you’ve just deployed a pristine CentOS Core Enterprise server, brimming with excitement to kick off your project. 🎯 Quick Answer: Install pip on CentOS Core Enterprise by first enabling EPEL: sudo yum install -y epel-release, then sudo yum install -y python3-pip. For CentOS without EPEL access, download get-pip.py with curl and run python3 get-pip.py. Picture this: you’ve just deployed a pristine CentOS Core Enterprise server, brimming with excitement to kick off your project. You fire up the terminal, ready to install essential Python packages with pip, but you hit an obstacle—no pip, no Python package manager, and no straightforward solution. It’s a frustrating roadblock that can halt productivity in its tracks. CentOS Core Enterprise is admired for its stability and security, but this focus on minimalism means you won’t find pip pre-installed. This intentional omission ensures a lean environment but leaves developers scrambling for modern Python tools. Fortunately, with the right steps, you can get pip up and running smoothly. Let me guide you through the process, covering everything from prerequisites to troubleshooting, so you can avoid the common pitfalls I’ve encountered over the years. Understanding the Challenge CentOS Core Enterprise is designed for enterprise-grade reliability. This means it prioritizes security and stability over convenience. By omitting tools like pip, CentOS ensures that the server environment remains focused on critical tasks without unnecessary software that could introduce vulnerabilities or clutter. While this approach is excellent for production environments where minimalism is key, it can be frustrating for developers who need a flexible setup to test, prototype, or build applications. Python, along with pip, has become the backbone of modern development workflows, powering everything from web apps to machine learning. Without pip, your ability to install Python packages is severely limited. To overcome this, you must understand the nuances of CentOS package management and the steps required to bring pip into your environment. Let’s dive into the step-by-step process. Step 1: Verify Your Python Installation Before diving into pip installation, it’s essential to check if Python is already installed on your system. CentOS Core Enterprise might include Python by default, but the version could vary. python --version python3 --version If these commands return a Python version, you’re in luck. However, if they return an error or an outdated version (e.g., Python 2.x), you’ll need to install or upgrade Python. Python 3 is the recommended version for most modern projects. Pro Tip: If you’re working on a legacy system that relies on Python 2, consider using virtualenv to isolate your Python environments and avoid conflicts. Step 2: Enable the EPEL Repository The Extra Packages for Enterprise Linux (EPEL) repository is a lifesaver when working with CentOS. It provides access to additional software packages, including pip. Enabling EPEL is the first critical step. sudo yum install epel-release Once installed, update your package manager to ensure it’s aware of the new repository: sudo yum update Warning: Ensure your system has an active internet connection before attempting to enable EPEL. If yum cannot connect to the repositories, check your network settings and proxy configurations. Step 3: Installing pip for Python 2 (If Required) While Python 2 has reached its end of life and is no longer officially supported, some legacy applications may still depend on it. If you’re in this situation, here’s how to install pip for Python 2: sudo yum install python-pip After installation, verify that pip is working: pip --version If the command returns the pip version, you’re good to go. However, keep in mind that many modern Python packages no longer support Python 2, so this path is only recommended for maintaining existing systems. Warning: Proceed with caution when using Python 2. It’s obsolete, and using it in new projects could introduce security risks. Step 4: Installing Python 3 and pip (Recommended) For new projects and modern applications, Python 3 is the gold standard. The good news is that installing Python 3 and pip on CentOS Core Enterprise is straightforward once EPEL is enabled. sudo yum install python3 This command installs Python 3 along with its bundled version of pip. After installation, you can upgrade pip to the latest version: sudo pip3 install --upgrade pip Verify the installation: python3 --version pip3 --version Both commands should return the respective versions of Python 3 and pip, confirming that everything is set up correctly. Pro Tip: Always upgrade pip after installing. The default version provided by yum is often outdated, which may cause compatibility issues with newer Python packages. Step 5: Troubleshooting Common Issues Despite following the steps, you might encounter some hiccups along the way. Here are common issues and how to resolve them: 1. yum Cannot Find EPEL If enabling EPEL fails, it’s often due to outdated yum repository data. Try running: sudo yum clean all sudo yum makecache Then, attempt to install EPEL again. 2. Dependency Errors During Installation Sometimes, installing Python or pip may fail due to unmet dependencies. Use the following command to identify and resolve them: sudo yum deplist python3 This command lists the required dependencies for Python 3. Install any missing ones manually. 3. pip Command Not Found If pip or pip3 isn’t recognized, ensure that the installation directory is included in your system’s PATH variable: export PATH=$PATH:/usr/local/bin To make this change permanent, add the line above to your ~/.bashrc file and reload it: source ~/.bashrc Step 6: Managing Python Environments Once Python and pip are installed, managing environments is crucial to avoid dependency conflicts. Tools like virtualenv and venv allow you to create isolated Python environments tailored to specific projects. Using venv (Built-in for Python 3) python3 -m venv myproject_env source myproject_env/bin/activate While activated, any Python packages you install will be isolated to this environment. To deactivate, simply run: deactivate Using virtualenv (Third-Party Tool) If you need to manage environments across Python versions, install virtualenv: sudo pip3 install virtualenv virtualenv myproject_env source myproject_env/bin/activate Again, use deactivate to exit the environment. Pro Tip: Consider using Pipenv for an all-in-one solution to manage dependencies and environments. Step 7: Additional Considerations for Production In production systems, you may need stricter control over your Python environment. Consider the following: System Integrity: Avoid installing libraries globally if possible. Use virtual environments to prevent conflicts between applications. Automation: Use configuration management tools like Ansible or Puppet to automate Python and pip installations across servers. Security: Always keep Python and pip updated to patch vulnerabilities. Regularly audit installed packages for outdated or potentially insecure versions. These practices will help you maintain a secure and efficient production environment. Quick Summary CentOS Core Enterprise doesn’t include pip by default, but enabling the EPEL repository unlocks access to modern Python tools. Python 3 is the recommended version for new projects, offering better performance, security, and compatibility. Always upgrade pip after installation to ensure compatibility with the latest Python packages. Use tools like venv or virtualenv to manage isolated Python environments and prevent dependency conflicts. If you encounter issues, focus on troubleshooting repository access, dependency errors, and system paths. With pip installed and configured, you’re ready to tackle anything from simple scrip


---
## How to Make HTTP Requests Through Tor with Python

- URL: https://orthogonal.info/how-to-make-requests-via-tor-in-python/
- Date: 2022-04-07
- Category: Python
- Summary: Route HTTP requests through the Tor network using Python. Covers SOCKS proxy setup, session handling, circuit rotation, and privacy best practices.

Why Use Tor for HTTP Requests? 📌 TL;DR: Why Use Tor for HTTP Requests? Picture this: you’re in the middle of a data scraping project, and suddenly, your IP address is blacklisted. Or perhaps you’re working on a privacy-first application where user anonymity is non-negotiable. 🎯 Quick Answer: Route Python HTTP requests through Tor by installing the requests and PySocks libraries, then configuring a SOCKS5 proxy at 127.0.0.1:9050: use requests.get(url, proxies={‘http’: ‘socks5h://127.0.0.1:9050’, ‘https’: ‘socks5h://127.0.0.1:9050’}). The ‘h’ suffix routes DNS through Tor too. Picture this: you’re in the middle of a data scraping project, and suddenly, your IP address is blacklisted. Or perhaps you’re working on a privacy-first application where user anonymity is non-negotiable. Tor (The Onion Router) is the perfect solution for both scenarios. It routes your internet traffic through a decentralized network of servers (nodes), obscuring its origin and making it exceptionally challenging to trace. Tor is not just a tool for bypassing restrictions; it’s a cornerstone of privacy on the internet. From journalists working in oppressive regimes to developers building secure applications, Tor is widely used for anonymity and bypassing censorship. It allows you to mask your IP address, avoid surveillance, and access region-restricted content. However, integrating Tor into your Python projects isn’t as straightforward as flipping a switch. It requires careful configuration and a solid understanding of the tools involved. Today, I’ll guide you through two robust methods to make HTTP requests via Tor: using the requests library with a SOCKS5 proxy and Using the stem library for advanced control. By the end, you’ll have all the tools you need to bring the power of Tor into your Python workflows. 🔐 Security Note: Tor anonymizes your traffic but does not encrypt it beyond the Tor network. Always use HTTPS to protect the data you send and receive. Getting Tor Up and Running Before we dive into Python code, we need to ensure that Tor is installed and running on your system. Here’s a quick rundown for different platforms: Linux: Install Tor via your package manager, e.g., sudo apt install tor. Start the service with sudo service tor start. Mac: Use Homebrew: brew install tor. Then start it with brew services start tor. Windows: Download the Tor Expert Bundle from the official Tor Project website, extract it, and run the tor.exe executable. By default, Tor runs a SOCKS5 proxy on 127.0.0.1:9050. This is the endpoint we’ll leverage to route HTTP requests through the Tor network. Pro Tip: After installing Tor, verify that it’s running by checking if the port 9050 is active. On Linux/Mac, use netstat -an | grep 9050. On Windows, use netstat -an | findstr 9050. Method 1: Using the requests Library with a SOCKS5 Proxy The simplest way to integrate Tor into your Python project is by configuring the requests library to use Tor’s SOCKS5 proxy. This approach is lightweight and straightforward but offers limited control over Tor’s features. Step 1: Install Required Libraries First, ensure you have the necessary dependencies installed. The requests library needs an additional component for SOCKS support: pip install requests[socks] Step 2: Configure a Tor-Enabled Session Create a reusable function to configure a requests session that routes traffic through Tor: import requests def get_tor_session(): session = requests.Session() session.proxies = { 'http': 'socks5h://127.0.0.1:9050', 'https': 'socks5h://127.0.0.1:9050' } return session The socks5h protocol ensures that DNS lookups are performed through Tor, adding an extra layer of privacy. Step 3: Test the Tor Connection Verify that your HTTP requests are being routed through the Tor network by checking your outbound IP address: session = get_tor_session() response = session.get("http://httpbin.org/ip") print("Tor IP:", response.json()) If everything is configured correctly, the IP address returned will differ from your machine’s regular IP address. This ensures that your request was routed through the Tor network. Warning: If you receive errors or no response, double-check that the Tor service is running and listening on 127.0.0.1:9050. Troubleshooting steps include restarting the Tor service and verifying your proxy settings. Method 2: Using the stem Library for Advanced Tor Control If you need more control over Tor’s capabilities, such as programmatically changing your IP address, the stem library is your go-to tool. It allows you to interact directly with the Tor process through its control port. Step 1: Install the stem Library Install the stem library using pip: pip install stem Step 2: Configure the Tor Control Port To use stem, you’ll need to enable the Tor control port (default: 9051) and set a control password. Edit your Tor configuration file (usually /etc/tor/torrc or torrc in the Tor bundle directory) and add: ControlPort 9051 HashedControlPassword <hashed_password> Generate a hashed password using the tor --hash-password command and paste it into the configuration file. Restart Tor for the changes to take effect. Step 3: Interact with the Tor Controller Use stem to authenticate and send commands to the Tor control port: from stem.control import Controller with Controller.from_port(port=9051) as controller: controller.authenticate(password='your_password') print("Connected to Tor controller") Step 4: Programmatically Change Your IP Address One of the most powerful features of stem is the ability to request a new Tor circuit (and thus a new IP address) with the SIGNAL NEWNYM command: from stem import Signal from stem.control import Controller with Controller.from_port(port=9051) as controller: controller.authenticate(password='your_password') controller.signal(Signal.NEWNYM) print("Requested a new Tor identity") Step 5: Combine stem with HTTP Requests You can marry the control capabilities of stem with the HTTP functionality of the requests library: import requests from stem import Signal from stem.control import Controller def get_tor_session(): session = requests.Session() session.proxies = { 'http': 'socks5h://127.0.0.1:9050', 'https': 'socks5h://127.0.0.1:9050' } return session with Controller.from_port(port=9051) as controller: controller.authenticate(password='your_password') controller.signal(Signal.NEWNYM) session = get_tor_session() response = session.get("http://httpbin.org/ip") print("New Tor IP:", response.json()) Troubleshooting Common Issues Tor not running: Ensure the Tor service is active. Restart it if necessary. Connection refused: Verify that the control port (9051) or SOCKS5 proxy (9050) is correctly configured. Authentication errors: Double-check your torrc file for the correct hashed password and restart Tor after modifications. Quick Summary Tor enhances anonymity by routing traffic through multiple nodes. The requests library with a SOCKS5 proxy is simple and effective for basic use cases. The stem library provides advanced control, including dynamic IP changes. Always use HTTPS to secure your data, even when using Tor. Troubleshooting tools like netstat and careful torrc configuration can resolve most issues. 🛠 Recommended Resources: Tools and books mentioned in (or relevant to) this article: TP-Link 5-Port 2.5G Switch — 5-port 2.5GbE unmanaged switch ($100-120) Ubiquiti U6+ WiFi 6 Access Point — WiFi 6 access point ($99) Cat8 Ethernet Cable 20ft — Shielded patch cables ($12) 📋 Disclosure: Some links are affiliate links. If you purchase through these links, I earn a small commission at no extra cost to you. I only recommend products I have personally used or thoroughly evaluated. 📚 Related Articles Home Network Segmentation with OPNsense: A Complete Guide Developer’s Ultimate Hardware Guide for 2026: Build Your Perfect Setup Ultimate Guide to Secure Remote Access for Your Homelab 📊 Free AI Market Intelligence Join Alpha Signal — AI-powered market research delivered daily. Narrative detection, 


---
## How to Extract and Work with HTML Using the Browser Console

- URL: https://orthogonal.info/how-to-get-html-code-from-console-of-a-website/
- Date: 2022-04-06
- Category: Quick Wins
- Summary: Extract and inspect HTML source code using your browser’s developer console. Learn DOM traversal, element selection, and live editing for web debugging.

The Hidden Power of Your Browser’s Console 📌 TL;DR: The Hidden Power of Your Browser’s Console Picture this: you’re debugging a webpage, and something just doesn’t look right. The CSS is on point, the JavaScript isn’t throwing errors, but the page still isn’t behaving the way it should. 🎯 Quick Answer: Extract HTML in the browser console using document.querySelector() or document.querySelectorAll() to target elements, then access .innerHTML, .outerHTML, or .textContent. Use copy(element.outerHTML) to copy results directly to clipboard for debugging or scraping. Picture this: you’re debugging a webpage, and something just doesn’t look right. The CSS is on point, the JavaScript isn’t throwing errors, but the page still isn’t behaving the way it should. At this point, you suspect something might be wrong with the actual HTML structure. How do you inspect or extract the raw HTML efficiently? The answer is simpler than you might think—it’s right in your browser’s console. The browser console isn’t just a debugging tool for developers; it’s a Swiss Army knife for analyzing websites, extracting data, and experimenting with web technologies in real-time. Today, I’ll walk you through how to extract HTML from a webpage using the browser console, tackle large or complex outputs, automate the process, and stay ethical while doing so. By the end, you’ll have a powerful new skill to add to your web development toolbox. What is document.documentElement.outerHTML? At the heart of this technique is the JavaScript property document.documentElement.outerHTML. This property allows you to retrieve the entire HTML structure of a webpage, starting from the <html> tag all the way to </html>. Think of it as a snapshot of the page’s DOM (Document Object Model) rendered as a string. Here’s a basic example to get started: // Retrieve the full HTML of the current page const pageHTML = document.documentElement.outerHTML; console.log(pageHTML); Running this in your browser’s console will print out the entire HTML of the page you’re viewing. But there’s much more to this than meets the eye. Let’s dive deeper into how you can use, modify, and automate this functionality. Warning: Always be cautious when running code in your browser console, especially on untrusted websites. Bad actors can use the console to execute malicious scripts. Never paste or run unverified code. Step-by-Step Guide to Extracting HTML Let’s break this down into actionable steps so you can extract HTML from any webpage confidently. 1. Open the Browser Console The first step is accessing the browser’s developer tools. Here’s how you can open the console in various browsers: Google Chrome: Press F12 or Ctrl+Shift+I (Windows/Linux) or Cmd+Option+I (Mac). Mozilla Firefox: Press F12 or Ctrl+Shift+K (Windows/Linux) or Cmd+Option+K (Mac). Microsoft Edge: Press F12 or Ctrl+Shift+I (Windows/Linux) or Cmd+Option+I (Mac). Safari: Enable the “Develop” menu in Preferences, then use Cmd+Option+C. 2. Run the Command Once the console is open, type the following command and hit Enter: document.documentElement.outerHTML The console will display the full HTML of the page. If the output is too long, use console.log to prevent truncation: console.log(document.documentElement.outerHTML); Pro Tip: If you find the output hard to read, copy it into a code editor like VS Code or use HTML Beautifiers to format it. 3. Copy and Save the HTML To copy the HTML, right-click on the console output and select “Copy” or use the keyboard shortcut Ctrl+C (Windows/Linux) or Cmd+C (Mac). You can paste it into a text editor or save it for further analysis. Working with Large HTML Outputs Sometimes, the webpage’s HTML is massive, and manually dealing with it becomes impractical. Here’s how to handle such scenarios effectively: 1. Save the HTML to a File Instead of dealing with the console output, you can create and download an HTML file directly using JavaScript: // Save the HTML to a downloadable file const html = document.documentElement.outerHTML; const blob = new Blob([html], { type: 'text/html' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'page.html'; link.click(); URL.revokeObjectURL(url); This script generates a file named page.html containing the full HTML of the page. It’s especially useful for archiving or sharing. 2. Extract Specific Sections Instead of extracting the entire HTML, you can target specific elements on the page: // Extract the body content only const bodyHTML = document.body.outerHTML; console.log(bodyHTML); // Extract a specific element by ID const elementHTML = document.getElementById('targetElement').outerHTML; console.log(elementHTML); // Extract all elements matching a CSS selector const selectedHTML = Array.from(document.querySelectorAll('.my-class')) .map(el => el.outerHTML) .join('\n'); console.log(selectedHTML); Pro Tip: Use browser extensions like SelectorGadget to identify CSS selectors for specific elements on a webpage. Automating HTML Extraction with Puppeteer If you need to extract HTML from multiple pages, automation is the way to go. One popular tool for this is Puppeteer, a Node.js library for controlling headless Chrome browsers. Here’s a sample script: // Puppeteer script to extract HTML const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://example.com'); const html = await page.evaluate(() => document.documentElement.outerHTML); console.log(html); await browser.close(); })(); This script launches a headless browser, navigates to the specified URL, and retrieves the page’s HTML. Puppeteer is invaluable for web scraping and testing. Common Pitfalls and Troubleshooting 1. Dynamic Content Some websites load content dynamically using JavaScript. In these cases, document.documentElement.outerHTML might not include all the rendered elements. Use Puppeteer or browser extensions to wait for content to load before extracting HTML. 2. Restricted Access Certain websites block scripts or use obfuscation techniques to hide their HTML. In such cases, use tools like Puppeteer or explore APIs the site might offer. 3. Truncated Console Output If the console truncates large outputs, use console.log or save the HTML directly to a file for complete access. Security and Ethical Considerations Extracting HTML is powerful, but it comes with responsibilities: Respect intellectual property rights. Don’t use extracted HTML to replicate or steal designs. Follow website terms of service. Some explicitly forbid scraping or data extraction. Don’t run untrusted scripts. Verify code before executing it in your browser console. Warning: Scraping websites without permission can lead to legal consequences. Always ensure you have the right to extract and use the data. Quick Summary document.documentElement.outerHTML is your go-to method for extracting a webpage’s full HTML. Use console.log or save the HTML to a file for managing large outputs. Target specific elements with document.querySelector or getElementById for precision extraction. Automate repetitive tasks using headless browsers like Puppeteer. Always consider ethical and legal implications when extracting HTML. With this knowledge, you’re now equipped to dive deeper into web development, debugging, and automation. What will you build or analyze next? 🛠 Recommended Resources: Tools and books mentioned in (or relevant to) this article: JavaScript: The Definitive Guide — Complete JS reference ($35-45) You Don’t Know JS Yet (book series) — Deep JavaScript knowledge ($30) Eloquent JavaScript — Modern intro to programming ($25) 📋 Disclosure: Some links are affiliate links. If you purchase through these links, I earn a small commission at no extra cost to you. I only recommend products I have personally used or thoroughly evaluated. 📚 Related Articles Vibe Coding Is a Security Nightmare — Here’s How to Survive It 


---
## Set Up Elasticsearch and Kibana on CentOS 7

- URL: https://orthogonal.info/setup-latest-elastic-search-and-kibana-on-centos7-in-april-2022/
- Date: 2022-04-05
- Category: Homelab
- Summary: Install and configure Elasticsearch and Kibana on CentOS 7. Covers Java setup, cluster configuration, index management, and Kibana dashboard basics.

Real-Time Search and Analytics: The Challenge 📌 TL;DR: Real-Time Search and Analytics: The Challenge Picture this: your team is tasked with implementing a solid real-time search and analytics solution, but time isn’t on your side. 🎯 Quick Answer: Set up Elasticsearch and Kibana on CentOS 7 by importing the Elastic GPG key, adding the Elastic 7.x yum repo, then running yum install elasticsearch kibana. Configure network.host in elasticsearch.yml and server.host in kibana.yml, then enable both services with systemctl. Picture this: your team is tasked with implementing a solid real-time search and analytics solution, but time isn’t on your side. You’ve got a CentOS 7 server at your disposal, and the pressure is mounting to get Elasticsearch and Kibana up and running quickly, securely, and efficiently. I’ve been there countless times, and through trial and error, I’ve learned exactly how to make this process smooth and sustainable. I’ll walk you through every essential step, with no shortcuts and actionable tips to avoid common pitfalls. Step 1: Prepare Your System for Elasticsearch Before diving into the installation, it’s crucial to ensure your CentOS 7 environment is primed for Elasticsearch. Neglecting these prerequisites can lead to frustrating errors down the line. Trust me—spending an extra 10 minutes here will save you hours later. Let’s break this down step by step. Networking Essentials Networking is the backbone of any distributed system, and Elasticsearch clusters are no exception. To avoid future headaches, it’s important to configure networking properly from the start. Set a static IP address: A dynamic IP can cause connectivity issues, especially in a cluster. Configure a static IP by editing the network configuration: sudo vi /etc/sysconfig/network-scripts/ifcfg-ens3 Update the file to include settings for a static IP, then restart the network service: sudo systemctl restart network Pro Tip: Use ip addr to confirm the IP address has been set correctly. Set a hostname: A clear, descriptive hostname helps with cluster management and debugging. Set a hostname like es-node1 using the following command: sudo hostnamectl set-hostname es-node1 Don’t forget to update /etc/hosts to map the hostname to your static IP address. Install Prerequisite Packages Elasticsearch relies on several packages to function properly. Installing them upfront will ensure a smoother setup process. Install essential utilities: Tools like wget and curl are needed for downloading files and testing connections: sudo yum install wget curl vim -y Install Java: Elasticsearch requires Java to run. While Elasticsearch 8.x comes with a bundled JVM, it’s a good idea to have Java installed system-wide for flexibility: sudo yum install java-1.8.0-openjdk.x86_64 -y Warning: If you decide to use the bundled JVM, avoid setting JAVA_HOME to prevent conflicts. Step 2: Install Elasticsearch 8.x on CentOS 7 Now that your system is ready, it’s time to install Elasticsearch. Version 8.x brings significant improvements, including built-in security features like TLS and authentication. Follow these steps carefully. Adding the Elasticsearch Repository The first step is to add the official Elasticsearch repository to your system. This ensures you’ll always have access to the latest version. Import the Elasticsearch GPG key: Verify the authenticity of the packages by importing the GPG key: sudo rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch Create the repository file: Add the Elastic repository by creating a new file: sudo vi /etc/yum.repos.d/elasticsearch.repo [elasticsearch] name=Elasticsearch repository for 8.x packages baseurl=https://artifacts.elastic.co/packages/8.x/yum gpgcheck=1 gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch enabled=0 autorefresh=1 type=rpm-md Pro Tip: Set enabled=0 to avoid accidental Elasticsearch updates during a system-wide yum update. Installing and Configuring Elasticsearch Once the repository is set up, you can proceed with the installation and configuration of Elasticsearch. Install Elasticsearch: Enable the repository and install Elasticsearch: sudo yum install --enablerepo=elasticsearch elasticsearch -y Configure Elasticsearch: Open the configuration file and make the following changes: sudo vi /etc/elasticsearch/elasticsearch.yml node.name: "es-node1" cluster.name: "my-cluster" network.host: 0.0.0.0 discovery.seed_hosts: ["127.0.0.1"] xpack.security.enabled: true This configuration enables a single-node cluster with basic security. Set JVM heap size: Adjust the JVM heap size for Elasticsearch: sudo vi /etc/elasticsearch/jvm.options -Xms4g -Xmx4g Pro Tip: Set the heap size to half of your system’s RAM but do not exceed 32GB for best performance. Start Elasticsearch: Enable and start the Elasticsearch service: sudo systemctl enable elasticsearch sudo systemctl start elasticsearch Verify the installation: Test the Elasticsearch setup by running: curl -X GET 'http://localhost:9200' Step 3: Install Kibana for Visualization Kibana provides a user-friendly interface for interacting with Elasticsearch. It allows you to visualize data, monitor cluster health, and manage security settings. Installing Kibana Follow these steps to install and configure Kibana on CentOS 7: Add the Kibana repository: sudo vi /etc/yum.repos.d/kibana.repo [kibana-8.x] name=Kibana repository for 8.x packages baseurl=https://artifacts.elastic.co/packages/8.x/yum gpgcheck=1 gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch enabled=1 autorefresh=1 type=rpm-md Install Kibana: sudo yum install kibana -y Configure Kibana: sudo vi /etc/kibana/kibana.yml server.host: "0.0.0.0" elasticsearch.hosts: ["http://localhost:9200"] xpack.security.enabled: true Start Kibana: sudo systemctl enable kibana sudo systemctl start kibana Access Kibana: Visit http://your-server-ip:5601 in your browser and log in using the enrollment token. Troubleshooting Common Issues Even with a thorough setup, issues can arise. Here are some common problems and their solutions: Elasticsearch won’t start: Check logs via journalctl -u elasticsearch for errors. Kibana cannot connect: Verify the elasticsearch.hosts setting in kibana.yml and ensure Elasticsearch is running. Cluster health is yellow: Add nodes or replicas to improve redundancy. Quick Summary Set up proper networking and prerequisites before installation. Use meaningful names for clusters and nodes. Enable Elasticsearch’s built-in security features. Monitor cluster health regularly to address issues proactively. By following this guide, you can confidently deploy Elasticsearch and Kibana on CentOS 7. Questions? Drop me a line—Max L. 🛠 Recommended Resources: Tools and books mentioned in (or relevant to) this article: TP-Link 5-Port 2.5G Switch — 5-port 2.5GbE unmanaged switch ($100-120) Ubiquiti U6+ WiFi 6 Access Point — WiFi 6 access point ($99) Cat8 Ethernet Cable 20ft — Shielded patch cables ($12) 📋 Disclosure: Some links are affiliate links. If you purchase through these links, I earn a small commission at no extra cost to you. I only recommend products I have personally used or thoroughly evaluated. 📚 Related Articles The Definitive Homelab Hardware Guide: Build Your Self-Hosting Dream in 2026 How to Configure a Used Aruba S2500 Switch and Optimize Its Ports Vibe Coding Is a Security Nightmare — Here’s How to Survive It 📊 Free AI Market Intelligence Join Alpha Signal — AI-powered market research delivered daily. Narrative detection, geopolitical risk scoring, sector rotation analysis. Join Free on Telegram → Pro with stock conviction scores: $5/mo { "@context": "https://schema.org", "@type": "TechArticle", "headline": "Set Up Elasticsearch and Kibana on CentOS 7", "url": "https://orthogonal.info/setup-latest-elastic-search-and-kibana-on-centos7-in-april-2022/", "datePublished": "2022-04-05T16:30:20", "dateModified": "2026-03-27T22:13:44", "author": { "@type": "Person", "name": "Max L", "jobTitle": "Security Engineer", "url": "htt


---
## Azure Service Bus with Python REST API (No SDK)

- URL: https://orthogonal.info/how-to-use-python-to-send-and-receive-an-azure-service-bus-notification-without-sdk/
- Date: 2022-04-04
- Category: Azure &amp; Cloud
- Summary: Send and receive Azure Service Bus messages using Python REST API without the SDK. Covers SAS token generation, queue operations, and error handling.

Why Bypass the Azure SDK for Service Bus? 📌 TL;DR: Why Bypass the Azure SDK for Service Bus? Azure Service Bus is a solid messaging platform that supports reliable communication between applications and services. 🎯 Quick Answer: Send messages to Azure Service Bus without the SDK by making HTTP POST requests to https://.servicebus.windows.net//messages with a SAS token in the Authorization header. Generate the token using HMAC-SHA256 over the resource URI with your shared access key. Azure Service Bus is a solid messaging platform that supports reliable communication between applications and services. While the official Python SDK simplifies interaction with Service Bus, there are compelling reasons to bypass it and directly interact with the REST API instead: Minimal Dependencies: The SDK introduces additional dependencies, which can be problematic for lightweight environments or projects with strict dependency management policies. Full HTTP Control: Direct API access allows you to customize headers, configure retries, and handle raw responses, giving you complete control over the HTTP lifecycle. Compatibility with Unique Environments: Non-standard environments, such as some serverless functions or niche container setups, may not support the Azure SDK. The REST API ensures compatibility. Deeper Insights: By working directly with the REST API, you gain a better understanding of how Azure Service Bus operates, which can be invaluable for debugging and advanced configurations. While the SDK is a convenient abstraction, bypassing it offers granular control and greater flexibility. This guide will walk you through sending and receiving messages from Azure Service Bus using Python’s requests library, without relying on the Azure SDK. Along the way, you’ll learn to authenticate using Shared Access Signature (SAS) tokens, troubleshoot common issues, and explore advanced use cases for the Service Bus REST API. Prerequisites: Setting Up for Success Before diving into implementation, ensure you have the following: Azure Subscription: Access to the Azure portal with an active subscription is required to provision and manage Service Bus resources. Service Bus Namespace: Create a Service Bus namespace in Azure. This namespace serves as a container for your queues, topics, and subscriptions. Queue Configuration: Set up a queue within your namespace. You will use this queue to send and receive messages. Authentication Credentials: Obtain the SAS key and key name for your namespace. These credentials will be used to generate authentication tokens for accessing the Service Bus. Python Environment: Install Python 3.6+ and the requests library. You can install the library via pip using pip install requests. Basic HTTP Knowledge: Familiarity with HTTP methods (GET, POST, DELETE) and JSON formatting will make the process easier to understand. Once you have these prerequisites in place, you’re ready to start building your Service Bus integration using the REST API. Step 1: Generating a Shared Access Signature (SAS) Token Authentication is a critical step when working with Azure Service Bus. To interact with the Service Bus REST API, you need to generate a Shared Access Signature (SAS) token. This token provides time-limited access to specific Service Bus resources. Below is a Python function to generate SAS tokens: import time import urllib.parse import hmac import hashlib import base64 def generate_sas_token(namespace, queue, key_name, key_value): """ Generate a SAS token for Azure Service Bus. """ resource_uri = f"https://{namespace}.servicebus.windows.net/{queue}" encoded_uri = urllib.parse.quote_plus(resource_uri) expiry = str(int(time.time()) + 3600) # Token valid for 1 hour string_to_sign = f"{encoded_uri}\n{expiry}" key = key_value.encode("utf-8") signature = hmac.new(key, string_to_sign.encode("utf-8"), hashlib.sha256).digest() encoded_signature = base64.b64encode(signature).decode() sas_token = f"SharedAccessSignature sr={encoded_uri}&sig={encoded_signature}&se={expiry}&skn={key_name}" return {"uri": resource_uri, "token": sas_token} Replace namespace, queue, key_name, and key_value with your actual Azure Service Bus details. The function returns a dictionary containing the resource URI and the SAS token. Pro Tip: Avoid hardcoding sensitive credentials like SAS keys. Instead, store them in environment variables and retrieve them using Python’s os.environ module. This ensures security and flexibility in your implementation. Step 2: Sending Messages to the Queue Once you have a SAS token, sending messages to the queue is straightforward. Use an HTTP POST request to send the message. Below is an example implementation: import requests def send_message_to_queue(token, message): """ Send a message to the Azure Service Bus queue. """ headers = { "Authorization": token["token"], "Content-Type": "application/json" } response = requests.post(f"{token['uri']}/messages", headers=headers, json=message) if response.status_code == 201: print("Message sent successfully!") else: print(f"Failed to send message: {response.status_code} - {response.text}") # Example usage namespace = "your-service-bus-namespace" queue = "your-queue-name" key_name = "your-sas-key-name" key_value = "your-sas-key-value" token = generate_sas_token(namespace, queue, key_name, key_value) message = {"content": "Hello, Azure Service Bus!"} send_message_to_queue(token, message) Ensure the message payload matches your queue’s expectations. For instance, you might send a JSON object or plain text depending on your application’s requirements. Warning: Ensure your SAS token includes Send permissions for the queue. Otherwise, the request will be rejected with a 403 error. Step 3: Receiving Messages from the Queue Receiving messages from the queue involves using an HTTP DELETE request to consume the next available message. Here’s an example implementation: def receive_message_from_queue(token): """ Receive a message from the Azure Service Bus queue. """ headers = {"Authorization": token["token"]} response = requests.delete(f"{token['uri']}/messages/head", headers=headers) if response.status_code == 200: print("Message received:") print(response.json()) # Assuming the message is in JSON format elif response.status_code == 204: print("No messages available in the queue.") else: print(f"Failed to receive message: {response.status_code} - {response.text}") # Example usage receive_message_from_queue(token) If no messages are available, the API will return a 204 status code, indicating the queue is empty. Processing received messages effectively is key to building a solid messaging system. Pro Tip: If your application needs to process messages asynchronously, use a loop or implement polling mechanisms to periodically check the queue for new messages. Troubleshooting Common Issues Interacting directly with the Service Bus REST API can present unique challenges. Here are solutions to common issues: 401 Unauthorized: This error often occurs when the SAS token is improperly formatted or has expired. Double-check the token generation logic and ensure your system clock is accurate. 403 Forbidden: This typically indicates insufficient permissions. Ensure that the SAS token has the appropriate rights (e.g., Send or Listen permissions). Timeout Errors: Network issues or restrictive firewall rules can cause timeouts. Verify that your environment allows outbound traffic to Azure endpoints. Message Size Limits: Azure Service Bus enforces size limits on messages (256 KB for Standard, 1 MB for Premium). Ensure your messages do not exceed these limits. Exploring Advanced Features Once you’ve mastered the basics, consider exploring these advanced features to enhance your Service Bus workflows: Dead-Letter Queues (DLQ): Messages that cannot be delivered or processed are sent to a DLQ. Use DLQs to debug issues or handle unprocessable messages. Message Sessions: Group related messages together for ordered processing. This is useful for w


---
## C# ConcurrentDictionary: Performance Best Practices

- URL: https://orthogonal.info/simple-tips-to-improve-c-concurrentdictionary-performance/
- Date: 2022-04-04
- Category: C# &amp; .NET
- Summary: Optimize C# ConcurrentDictionary for high-throughput scenarios. Covers partitioning, lock contention, sizing strategies, and thread-safe update patterns.

Performance bottlenecks in multi-threaded applications are a common challenge for developers. If you’ve ever struggled with optimizing C#’s ConcurrentDictionary, you’re not alone. While this data structure is a powerful tool for managing shared state across threads, it can easily become a source of inefficiency if misused. I’ll walk you through actionable tips, common pitfalls, and advanced techniques to maximize the performance and reliability of ConcurrentDictionary in your applications. Understanding When to Use ConcurrentDictionary 📌 TL;DR: Performance bottlenecks in multi-threaded applications are a common challenge for developers. If you’ve ever struggled with optimizing C#’s ConcurrentDictionary , you’re not alone. 🎯 Quick Answer: Optimize ConcurrentDictionary in C# by using GetOrAdd() and AddOrUpdate() instead of manual lock-check-add patterns. Set concurrencyLevel to your CPU core count and initial capacity to expected size. Avoid frequent Count or ToArray() calls — they lock all internal segments and kill throughput. The first step in mastering ConcurrentDictionary is understanding its purpose. It’s designed for scenarios where multiple threads need to read and write to a shared collection without explicit locking. However, this thread-safety comes at a cost—higher memory usage and slightly reduced performance compared to Dictionary<TKey, TValue>. Pro Tip: If your application has mostly read operations with rare writes, consider using ReaderWriterLockSlim with a regular Dictionary for better performance. When to Avoid ConcurrentDictionary Not every scenario calls for ConcurrentDictionary. In single-threaded or read-heavy environments, a regular Dictionary is faster and uses less memory. Choose ConcurrentDictionary only when: Multiple threads need simultaneous read and write access. You want to avoid managing explicit locks. Thread safety is a priority over raw performance. For example, imagine a scenario where your application processes large datasets in a single thread. Using ConcurrentDictionary in such cases is inefficient and overkill. Instead, a simple Dictionary will suffice and perform better. Optimize Performance with GetOrAdd A common mistake when using ConcurrentDictionary is manually checking for a key’s existence before adding or retrieving values. This approach undermines the built-in thread safety of the dictionary and introduces unnecessary overhead. Bad Practice if (!_concurrentDictionary.TryGetValue(key, out var value)) { value = new ExpensiveObject(); _concurrentDictionary.TryAdd(key, value); } The code above performs redundant checks, which can lead to race conditions in high-concurrency scenarios. Instead, leverage GetOrAdd, which atomically retrieves a value if it exists or adds it if it doesn’t: Recommended Practice var value = _concurrentDictionary.GetOrAdd(key, k => new ExpensiveObject()); This single call ensures thread safety and eliminates the need for manual checks. It’s concise, efficient, and less error-prone. Fine-Tuning ConcurrencyLevel The ConcurrentDictionary is internally divided into segments, each protected by a lock. The ConcurrencyLevel property determines the number of segments, which defaults to four times the number of CPU cores. While this default works for many scenarios, it can lead to excessive memory usage in cloud environments with dynamic CPU counts. Setting a Custom Concurrency Level If you know the expected number of concurrent threads, you can set the concurrency level manually to reduce overhead: var dictionary = new ConcurrentDictionary<string, int>( concurrencyLevel: 4, // Adjust based on your workload capacity: 1000 // Pre-allocate space for better performance ); Warning: Setting a concurrency level too low can increase contention, while setting it too high wastes memory. Perform benchmarks to find the best value for your use case. For instance, if your application expects 8 concurrent threads, setting a concurrency level of 8 ensures best partitioning. However, if you increase the level to 64 unnecessarily, each partition would consume memory without providing any tangible performance benefits. Efficient Enumeration: Avoid Keys and Values Accessing .Keys or .Values in ConcurrentDictionary is expensive because these operations lock the entire dictionary and create new collections. Instead, iterate directly over KeyValuePair entries: Inefficient Access foreach (var key in _concurrentDictionary.Keys) { Console.WriteLine(key); } This approach locks the dictionary and creates a temporary list of keys. Instead, use this: Efficient Access foreach (var kvp in _concurrentDictionary) { Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}"); } By iterating over KeyValuePair entries, you avoid unnecessary locks and reduce memory allocations. Minimize Expensive Operations Some ConcurrentDictionary operations, like Count and ContainsKey, can be performance bottlenecks in high-concurrency scenarios. Let’s explore how to minimize their impact. Avoid Using Count in Critical Paths The Count property locks all segments of the dictionary, making it slow and unsuitable for performance-critical code. For lock-free tracking of item counts, use Interlocked operations: class ConcurrentCounter { private int _count; public void Increment() => Interlocked.Increment(ref _count); public void Decrement() => Interlocked.Decrement(ref _count); public int GetCount() => _count; } Wrap your dictionary with a custom class that uses ConcurrentCounter for efficient count management. For example, if your application frequently checks the size of a dictionary to make decisions, replacing Count with an atomic counter will significantly improve performance. Reconsider ContainsKey Using ContainsKey before operations like TryRemove can improve performance, but only if the dictionary is relatively small. For large dictionaries, the additional lookup may negate the benefits. If you know the key is likely to exist, skip ContainsKey and go straight to TryRemove: if (_concurrentDictionary.TryRemove(key, out var value)) { // Process removed value } Common Pitfalls and Troubleshooting Overusing ConcurrentDictionary A common mistake is using ConcurrentDictionary as the default choice for all dictionary needs. Remember, it’s slower and more memory-intensive than Dictionary. Use it only when thread safety is required. Deadlocks with External Locks If you combine ConcurrentDictionary with external locking mechanisms (like lock statements), you risk introducing deadlocks. Always rely on the dictionary’s built-in thread safety instead of adding redundant locks. Ignoring Capacity Planning Failure to pre-allocate capacity can lead to frequent resizing, which is expensive in multi-threaded environments. Initialize the dictionary with a reasonable capacity to avoid this issue. Advanced Techniques Lazy Initialization of Values For expensive-to-create values, use Lazy<T> to defer initialization: var dictionary = new ConcurrentDictionary<string, Lazy<ExpensiveObject>>(); var value = dictionary.GetOrAdd("key", k => new Lazy<ExpensiveObject>(() => new ExpensiveObject())).Value; This approach ensures that the value is only created once, even in highly concurrent scenarios. Custom Equality Comparers If your keys are complex objects, use a custom equality comparer to optimize lookups: var dictionary = new ConcurrentDictionary<MyComplexKey, string>( new MyComplexKeyEqualityComparer() ); Implement IEqualityComparer<T> for your key type to provide efficient hash code calculations and equality checks. For example, if your keys include composite data such as strings and integers, implementing a comparer can significantly speed up lookups and reduce collisions. Quick Summary Use ConcurrentDictionary only when thread safety is essential—opt for Dictionary in single-threaded or read-heavy scenarios. Replace manual existence checks with GetOrAdd for atomic operations. Customize ConcurrencyLevel and capacity based on your workload to minimize overhead. Avoid 


---
## Mastering `scp`: Securely Transfer Files Like a Pro

- URL: https://orthogonal.info/how-to-move-files-around-with-scp/
- Date: 2022-04-03
- Category: Quick Wins
- Summary: Master the scp command for secure file transfers between servers. Covers syntax, recursive copy, port configuration, key-based auth, and common examples.

scp (Secure Copy Protocol) can save the day. It’s a simple, efficient, and secure command-line tool for transferring files between systems over SSH. But while scp is easy to use, mastering it involves more than just the basic syntax. I’ll show you how to use scp effectively and securely. From basic file transfers to advanced options, troubleshooting, and real-world examples, we’ll cover everything you need to know to wield scp like a seasoned sysadmin. Understanding scp 📌 TL;DR: scp (Secure Copy Protocol) can save the day. It’s a simple, efficient, and secure command-line tool for transferring files between systems over SSH. But while scp is easy to use, mastering it involves more than just the basic syntax. 🎯 Quick Answer: Use scp for secure file transfers: scp file user@host:/path for upload, scp user@host:/path file for download. Use -r for directories, -P for custom ports, -C for compression, and -i for identity keys. For large recurring transfers, prefer rsync over scp for delta sync. scp stands for Secure Copy Protocol. It leverages SSH (Secure Shell) to transfer files securely between local and remote systems. The encryption provided by SSH ensures that your data is protected during transit, making scp a reliable choice for transferring sensitive files. One of the reasons scp is so popular is its simplicity. Unlike more feature-rich tools like rsync, scp doesn’t require extensive setup. If you have SSH access to a remote server, you can start using scp immediately. However, simplicity comes at a cost: scp lacks some advanced features like incremental file transfers. We’ll discuss when to use scp and when to opt for alternatives later in the article. Basic Usage: Downloading Files One of the most common use cases for scp is downloading files from a remote server to your local machine. Here’s the basic syntax: scp -i ~/.ssh/id_rsa user@remote-server:/path/to/remote/file /path/to/local/destination Here’s a breakdown of the command: -i ~/.ssh/id_rsa: Specifies the SSH private key for authentication. user@remote-server: The username and hostname (or IP) of the remote server. :/path/to/remote/file: The absolute path to the file on the remote server. /path/to/local/destination: The local directory where the file will be saved. After running this command, the file from the remote server will be downloaded to your specified local destination. Example: Downloading Logs for Debugging Imagine you’re diagnosing a production issue and need to analyze Nginx logs locally. Here’s how you can download them: scp -i ~/.ssh/id_rsa [email protected]:/var/log/nginx/access.log ./access.log If the log file is large, you can use the -C option to compress the file during transfer: scp -C -i ~/.ssh/id_rsa [email protected]:/var/log/nginx/access.log ./access.log Pro Tip: Always use absolute paths for remote files to avoid confusion, especially when transferring files from deep directory structures. Uploading Files Uploading files to a remote server is just as straightforward. The syntax is similar, but the source and destination paths are reversed: scp -i ~/.ssh/id_rsa /path/to/local/file user@remote-server:/path/to/remote/destination For example, to upload a configuration file, you might run: scp -i ~/.ssh/id_rsa ./nginx.conf [email protected]:/etc/nginx/nginx.conf After uploading the file, apply the changes by restarting the service: ssh -i ~/.ssh/id_rsa [email protected] "sudo systemctl reload nginx" Warning: Ensure the destination directory exists and has appropriate permissions. Otherwise, the upload will fail. Advanced Options scp includes several useful options to enhance functionality: -C: Compresses files during transfer to speed up large file transfers. -r: Recursively copies entire directories. -P: Specifies a custom SSH port. -p: Preserves file modification and access timestamps. Example: Copying Directories To upload an entire directory to a remote server: scp -r -i ~/.ssh/id_rsa ./my_project [email protected]:/home/admin/ This command transfers the my_project directory and all its contents. Pro Tip: Use -p to retain file permissions and timestamps during transfer. Example: Transferring Files Between Two Remote Servers What if you need to transfer a file directly from one remote server to another? scp can handle that too: scp -i ~/.ssh/id_rsa user1@remote1:/path/to/file user2@remote2:/path/to/destination In this scenario, scp acts as the bridge, securely transferring the file between two remote servers without downloading it to your local machine. Troubleshooting Common Issues Although scp is reliable, you may encounter issues. Here’s how to address common problems: Permission Denied Ensure your SSH key has correct permissions: chmod 600 ~/.ssh/id_rsa. Verify your user account has appropriate permissions on the remote server. Connection Timeout Check if the SSH service is running on the remote server. Verify you’re using the correct IP address and port. Slow Transfers Use -C to enable compression. Consider switching to rsync for large or incremental transfers. File Integrity Issues To ensure the file is correctly transferred, compare checksums before and after the transfer using md5sum or sha256sum. If you notice corrupted files, try using rsync with checksum verification. When to Use scp (and When Not To) scp is ideal for quick, ad-hoc file transfers, especially when simplicity is key. However, it’s not always the best tool: For large datasets or frequent transfers, rsync is more efficient. For automated workflows, tools like ansible or sftp may be better suited. If you need incremental synchronization or partial file updates, rsync excels in these scenarios. For transferring files over HTTP or a browser, consider alternatives like curl or wget. Security Best Practices While scp leverages SSH for security, you can take additional steps to harden your file transfers: Use strong SSH keys with a passphrase instead of passwords. Restrict SSH access to specific IPs using firewall rules. Regularly update your SSH server and client to patch vulnerabilities. Disable root access on the remote server and use a non-root user for file transfers. Monitor logs for unauthorized access attempts. Quick Summary scp provides a secure way to transfer files over SSH. Advanced options like -C, -r, and -p enhance functionality. Use SSH keys instead of passwords for better security. Be mindful of permissions and directory structures to avoid errors. Consider alternatives like rsync for more complex transfer needs. Leverage compression and checksum verification for faster and safer transfers. Now that you’re equipped with scp knowledge, go forth and transfer files securely and efficiently! 🛠 Recommended Resources: Tools and books mentioned in (or relevant to) this article: The Linux Command Line, 2nd Edition — Complete intro to Linux CLI ($20-30) Linux Bible, 10th Edition — Complete Linux guide ($35) UNIX and Linux System Administration Handbook — The sysadmin bible ($45) 📋 Disclosure: Some links are affiliate links. If you purchase through these links, I earn a small commission at no extra cost to you. I only recommend products I have personally used or thoroughly evaluated. 📚 Related Articles Home Network Segmentation with OPNsense: A Complete Guide Solving Homelab Bottlenecks: Why Upgrading to a 2.5G Switch is Game-Changing How to Protect Your Homelab from Dust: A Practical Guide 📊 Free AI Market Intelligence Join Alpha Signal — AI-powered market research delivered daily. Narrative detection, geopolitical risk scoring, sector rotation analysis. Join Free on Telegram → Pro with stock conviction scores: $5/mo { "@context": "https://schema.org", "@type": "TechArticle", "headline": "Mastering `scp`: Securely Transfer Files Like a Pro", "url": "https://orthogonal.info/how-to-move-files-around-with-scp/", "datePublished": "2022-04-03T21:12:11", "dateModified": "2026-03-27T22:13:45", "author": { "@type": "Person", "name": "Max L", "jobTitle": "Security Engineer", "url": "https://


---
## Remote Command Execution with SSH: Complete Guide

- URL: https://orthogonal.info/how-to-execute-a-commands-or-a-script-via-ssh/
- Date: 2022-04-03
- Category: Quick Wins
- Summary: Execute commands and scripts on remote servers via SSH. Covers single commands, heredoc scripts, key-based auth, error handling, and automation tips.

Picture This: The Power of Remote Command Execution 📌 TL;DR: Picture This: The Power of Remote Command Execution Imagine you’re managing a fleet of servers spread across multiple data centers. Something goes awry, and you need to diagnose or fix an issue—fast. 🎯 Quick Answer: Execute remote commands via SSH with: ssh user@host ‘command’ for single commands, or ssh user@host ‘bash -s’ Imagine you’re managing a fleet of servers spread across multiple data centers. Something goes awry, and you need to diagnose or fix an issue—fast. Do you want to fumble through a web interface or launch a resource-heavy remote desktop session? I know I wouldn’t. Instead, I rely on SSH (Secure Shell), a powerful tool that lets you execute commands on remote machines with precision, speed, and simplicity. SSH isn’t just for logging into remote systems. It’s a cornerstone for automation, troubleshooting, and deployment. Whether you’re a seasoned sysadmin or a developer dabbling in server management, knowing how to execute commands or scripts remotely via SSH is an absolutea big improvement. Let’s dive deep into this essential skill. What is SSH? SSH, short for Secure Shell, is a cryptographic network protocol that allows secure communication between two systems. It enables users to access and manage remote machines over an encrypted connection, ensuring data integrity and security. Unlike traditional remote protocols that transmit data in plain text, SSH uses robust encryption algorithms, making it a preferred choice for modern IT operations. At its core, SSH is a versatile tool. While many associate it with secure login to remote servers, its applications go far beyond that. From file transfers using scp and rsync to tunneling traffic securely and running commands remotely, SSH is an indispensable part of any system administrator’s toolkit. How Does SSH Work? To understand the power of SSH, it helps to know a little about how it works. SSH operates using a client-server model. Here’s a breakdown of the process: Authentication: When you initiate an SSH connection, the client authenticates itself to the server. This is typically done using a password or SSH key pair. Encryption: Once authenticated, all communication between the client and the server is encrypted. This ensures that sensitive data, like passwords or commands, cannot be intercepted by malicious actors. Command Execution: After establishing the connection, you can execute commands on the remote server. The server processes these commands and sends the output back to the client. SSH uses port 22 by default, but this can be configured to use a different port for added security. It also supports a range of authentication methods, including password-based login, public key authentication, and even multi-factor authentication for enhanced security. Running Single Commands via SSH Need to quickly check the status or metrics of your remote server? Single-command execution is your best friend. Using SSH, you can run a command on a remote host and instantly receive the output in your local terminal. ssh user@remote_host 'uptime' This example retrieves the uptime of remote_host. The command inside single quotes runs directly on the remote machine, and its output gets piped back to your local terminal. Pro Tip: Use quotes to enclose the command. This prevents your local shell from interpreting special characters before they reach the remote host. Want something more complex? Here’s how you can list the top 5 processes consuming CPU: ssh user@remote_host "ps -eo pid,comm,%cpu --sort=-%cpu | head -n 5" Notice the use of double quotes for commands containing spaces and special characters. Always test your commands locally before running them remotely to avoid unexpected results. Executing Multiple Commands in One SSH Session Sometimes, a single command won’t cut it—you need to execute a series of commands. Instead of logging in and typing each manually, you can bundle them together. The simplest way is to separate commands with a semicolon: ssh user@remote_host 'cd /var/log; ls -l; cat syslog' However, if your sequence is more complex, a here document is a better choice: ssh user@remote_host << 'EOF' cd /var/log ls -l cat syslog EOF Warning: Ensure the EOF delimiter is unindented and starts at the beginning of the line. Indentation or extra spaces will cause errors. This approach is clean, readable, and perfect for scripts where you need to execute a batch of commands remotely. It also helps avoid the hassle of escaping special characters. Running Local Scripts on Remote Machines What if you have a script on your local machine that you need to execute remotely? Instead of copying the script to the remote host first, you can stream it directly to the remote shell: ssh user@remote_host 'bash -s' < local_script.sh Here, local_script.sh is piped to the remote shell, which executes it line by line. Pro Tip: If your script requires arguments, you can pass them after bash -s: ssh user@remote_host 'bash -s' -- arg1 arg2 < local_script.sh In this example, arg1 and arg2 are passed as arguments to local_script.sh, making it as versatile as running the script locally. Advanced Techniques: Using SSH for Automation For complex workflows or automation, consider these advanced techniques: Using SSH with Cron Jobs Want to execute commands automatically at scheduled intervals? Combine SSH with cron jobs: 0 * * * * ssh user@remote_host 'df -h / >> /var/log/disk_usage.log' This example logs disk usage to a file on the remote host every hour. SSH and Environment Variables Remote environments often differ from your local setup. If your commands rely on specific environment variables, explicitly set them: ssh user@remote_host 'export PATH=/custom/path:$PATH; my_command' Alternatively, you can run your commands in a specific shell: ssh user@remote_host 'source ~/.bash_profile; my_command' Warning: Always check the remote shell type and configuration when troubleshooting unexpected behavior. Using SSH in Scripts SSH is a powerful ally for scripting. For example, you can create a script that checks the health of multiple servers: #!/bin/bash for server in server1 server2 server3; do ssh user@$server 'uptime' done This script loops through a list of servers and retrieves their uptime, making it easy to monitor multiple machines at once. Troubleshooting SSH Command Execution Things don’t always go smoothly with SSH. Here are common issues and their resolutions: SSH Authentication Failures: Ensure your public key is correctly added to the ~/.ssh/authorized_keys file on the remote host. Also, verify permissions (700 for .ssh and 600 for authorized_keys). Command Not Found: Double-check the remote environment. If a command isn’t in the default PATH, provide its full path or set the PATH explicitly. Script Execution Errors: Use bash -x for debugging to trace the execution line by line. Connection Timeouts: Ensure the remote host allows SSH traffic and verify firewall or network configurations. Best Practices for Secure and Efficient SSH Usage To make the most of SSH while keeping your systems secure, follow these best practices: Always Use SSH Keys: Password authentication is risky, especially in scripts. Generate an SSH key pair using ssh-keygen and configure public key authentication. Quote Commands Properly: Special characters can wreak havoc if not quoted correctly. Use single or double quotes as needed. Test Commands Locally: Before running destructive commands remotely (e.g., rm -rf), test them in a local environment. Enable Logging: Log both input and output of remote commands for auditing and debugging purposes. Verify Exit Codes: SSH returns the exit status of the remote command. Always check this value in scripts to handle errors effectively. Beyond the Basics: Exploring SSH Tunneling SSH isn’t limited to command execution—it also supports powerful features like tunneling. SSH tunneling enables you to securely forward ports between a local and 


---
## Expert Guide: Migrating ZVols and Datasets Between ZFS Pools

- URL: https://orthogonal.info/how-to-move-zvol-or-dataset-to-another-pool/
- Date: 2022-04-03
- Category: Homelab
- Summary: Move ZFS ZVols and datasets between pools safely. Step-by-step guide covering snapshots, zfs send/receive, verification, and avoiding common data loss.

Pro Tip: If you’ve ever faced the challenge of moving ZFS datasets or ZVols, you know it’s more than just a copy-paste job. A single mistake can lead to downtime or data corruption. I’ll walk you through the entire process step-by-step, sharing practical advice from real-world scenarios. Why Migrate ZFS Datasets or ZVols? 📌 TL;DR: Pro Tip: If you’ve ever faced the challenge of moving ZFS datasets or ZVols, you know it’s more than just a copy-paste job. A single mistake can lead to downtime or data corruption. I’ll walk you through the entire process step-by-step, sharing practical advice from real-world scenarios. 🎯 Quick Answer: Migrate ZFS datasets between pools with: zfs snapshot pool1/dataset@migrate, then zfs send pool1/dataset@migrate | zfs receive pool2/dataset. For ZVols, use the same send/receive pipeline. Add -R for recursive datasets and use -i for incremental sends to minimize downtime. Imagine upgrading your storage infrastructure with faster drives or running out of space on your current ZFS pool. Migrating ZFS datasets or ZVols to a different pool allows you to reorganize your storage without rebuilding everything from scratch. Whether you’re performing an upgrade, consolidating storage, or implementing better redundancy, ZFS provides robust tools to make the transfer seamless and secure. There are many scenarios that might necessitate a ZFS dataset or ZVol migration, such as: Hardware Upgrades: Transitioning to larger, faster drives or upgrading RAID configurations. Storage Consolidation: Combining datasets from multiple pools into a single location for easier management. Disaster Recovery: Moving data to a secondary site or server to ensure business continuity. Resource Optimization: Balancing the storage load across multiple pools to improve performance. Warning: ZFS snapshots and transfers do not encrypt data by default. If your data is sensitive, ensure encryption is applied on the target pool or use a secure transport layer like SSH. Understanding ZFS Terminology Before diving into commands, here’s a quick refresher: ZVol: A block device created within a ZFS pool, often used for virtual machines or iSCSI targets. These are particularly useful for environments where block-level storage is required. Dataset: A filesystem within a ZFS pool used to store files and directories. These are highly flexible and support features like snapshots, compression, and quotas. Pool: A collection of physical storage devices managed by ZFS, serving as the foundation for datasets and ZVols. Pools abstract the underlying hardware, allowing ZFS to provide advanced features like redundancy, caching, and snapshots. These components work together, and migrating them involves transferring data from one pool to another, either locally or across systems. The key commands for this process are zfs snapshot, zfs send, and zfs receive. Step 1: Preparing for Migration 1.1 Check Space Availability Before initiating a migration, it is crucial to ensure that the target pool has enough free space to accommodate the dataset or ZVol being transferred. Running out of space mid-transfer can lead to incomplete migrations and potential data integrity issues. Use the zfs list command to verify sizes: # Check source dataset or ZVol size zfs list pool1/myVol # Check available space in the target pool zfs list pool2 Warning: If your source dataset has compression enabled, ensure the target pool supports the same compression algorithm. Otherwise, the transfer may require significantly more space than anticipated. 1.2 Create Snapshots Snapshots are an essential part of ZFS data migration. They create a consistent, point-in-time copy of your data, ensuring that the transfer process does not affect live operations. Always use descriptive naming conventions for your snapshots, such as including the date or purpose of the snapshot. # Snapshot for ZVol zfs snapshot -r pool1/myVol@migration # Snapshot for dataset zfs snapshot -r pool1/myDataset@migration Pro Tip: Use descriptive names for snapshots, such as @migration_20231015, to make them easier to identify later, especially if you’re managing multiple migrations. Step 2: Transferring Data 2.1 Moving ZVols Transferring ZVols involves using the zfs send and zfs receive commands. The process streams data from the source pool to the target pool efficiently: # Transfer snapshot to target pool zfs send pool1/myVol@migration | zfs receive -v pool2/myVol Adding the -v flag to zfs receive provides verbose output, enabling you to monitor the progress of the transfer and diagnose any issues that may arise. 2.2 Moving Datasets The procedure for migrating datasets is similar to that for ZVols. For example: # Transfer dataset snapshot zfs send pool1/myDataset@migration | zfs receive -v pool2/myDataset Pro Tip: For network-based transfers, pipe the commands through SSH to ensure secure transmission: zfs send pool1/myDataset@migration | ssh user@remotehost zfs receive -v pool2/myDataset 2.3 Incremental Transfers For large datasets or ZVols, incremental transfers are an effective way to minimize downtime. Instead of transferring all the data at once, only changes made since the last snapshot are sent: # Initial transfer zfs snapshot -r pool1/myDataset@initial zfs send pool1/myDataset@initial | zfs receive -v pool2/myDataset # Incremental transfer zfs snapshot -r pool1/myDataset@incremental zfs send -i pool1/myDataset@initial pool1/myDataset@incremental | zfs receive -v pool2/myDataset Warning: Ensure that all intermediate snapshots in the transfer chain exist on both the source and target pools. Deleting these snapshots can break the chain and make incremental transfers impossible. Step 3: Post-Migration Cleanup 3.1 Verify Data Integrity After completing the migration, verify that the data on the target pool matches your expectations. Use zfs list to confirm the presence and size of the migrated datasets or ZVols: # Confirm data existence on target pool zfs list pool2/myVol zfs list pool2/myDataset You can also use checksums or file-level comparisons for additional verification. 3.2 Remove Old Snapshots If the snapshots on the source pool are no longer needed, you can delete them to free up space: # Delete snapshot zfs destroy pool1/myVol@migration zfs destroy pool1/myDataset@migration Pro Tip: Retain snapshots on the target pool for a few days as a safety net before performing deletions. This ensures you can revert to these snapshots if something goes wrong post-migration. Troubleshooting Common Issues Transfer Errors If zfs send fails, check that the snapshot exists on the source pool: # Check snapshots zfs list -t snapshot Insufficient Space If the target pool runs out of space during a transfer, consider enabling compression or freeing up unused storage: # Enable compression zfs set compression=lz4 pool2 Slow Transfers For sluggish transfers, use mbuffer to optimize the data stream and reduce bottlenecks: # Accelerate transfer with mbuffer zfs send pool1/myDataset@migration | mbuffer -s 128k | zfs receive pool2/myDataset Performance Optimization Tips Parallel Transfers: Break large datasets into smaller pieces and transfer them concurrently to speed up the process. Compression: Use built-in compression with -c in zfs send to reduce the amount of data being transmitted. Monitor Activity: Use tools like zpool iostat or zfs list to track performance and balance disk load during migration. Quick Summary Always create snapshots before transferring data to ensure consistency and prevent data loss. Verify available space on the target pool to avoid transfer failures. Use incremental transfers for large datasets to minimize downtime and reduce data transfer volumes. Secure network transfers with SSH or other encryption methods to protect sensitive data. Retain snapshots on the target pool temporarily as a safety net before finalizing the migration. Migrating ZFS datasets or ZVols doesn’t have to be daunting. With the right preparation, commands, and 


---
## Setup k3s on CentOS 7: Easy Tutorial for Beginners

- URL: https://orthogonal.info/setup-k3s-on-centos-7/
- Date: 2022-04-03
- Category: Homelab
- Summary: Learn how to setup k3s on CentOS 7 with this step-by-step tutorial. Perfect for beginners looking for a lightweight Kubernetes solution.

Picture this: you’re tasked with deploying Kubernetes on CentOS 7 in record time. Maybe it’s for a pet project, a lab environment, or even production. You’ve heard of k3s, the lightweight Kubernetes distribution, but you’re unsure where to start. Don’t worry—I’ve been there, and I’m here to help. I’ll walk you through setting up k3s on CentOS 7 step by step. We’ll cover prerequisites, installation, troubleshooting, and even a few pro tips to make your life easier. By the end, you’ll have a solid Kubernetes setup ready to handle your workloads. Why Choose k3s for CentOS 7? 📌 TL;DR: Picture this: you’re tasked with deploying Kubernetes on CentOS 7 in record time. Maybe it’s for a pet project, a lab environment, or even production. You’ve heard of k3s, the lightweight Kubernetes distribution, but you’re unsure where to start. 🎯 Quick Answer: Install k3s on CentOS 7 with a single command: curl -sfL https://get.k3s.io | sh -. K3s runs a full Kubernetes cluster in under 512MB RAM. Verify with sudo k3s kubectl get nodes. It bundles containerd, CoreDNS, and Traefik by default. Kubernetes is a fantastic tool, but its complexity can be daunting, especially for smaller setups. k3s simplifies Kubernetes without sacrificing core functionality. Here’s why k3s is a great choice for CentOS 7: Lightweight: k3s has a smaller footprint compared to full Kubernetes distributions. It removes unnecessary components, making it faster and more efficient. Easy to Install: A single command gets you up and running, eliminating the headache of lengthy installation processes. Built for Edge and IoT: It’s perfect for resource-constrained environments like edge devices, Raspberry Pi setups, or virtual machines with limited resources. Fully CNCF Certified: Despite its simplicity, k3s adheres to Kubernetes standards, ensuring compatibility with Kubernetes-native tools and configurations. Automatic Upgrades: k3s includes a built-in upgrade mechanism, making it easier to keep your cluster updated without manual intervention. Whether you’re setting up a development environment or a lightweight production cluster, k3s is the ideal solution for CentOS 7 due to its ease of use and reliability. Now, let’s dive into the setup process. Step 1: Preparing Your CentOS 7 System Before installing k3s, your CentOS 7 server needs to meet a few prerequisites. Skipping these steps can lead to frustrating errors down the line. Proper preparation ensures a smooth installation and optimizes your cluster’s performance. Update Your System First, ensure your system is up to date. This keeps packages current and eliminates potential issues caused by outdated dependencies. Run the following commands: sudo yum update -y sudo yum upgrade -y After completing the updates, reboot your server to apply any pending changes to the kernel or system libraries: sudo reboot Set a Static IP Address For a stable cluster, assign a static IP to your server. This ensures consistent communication between nodes. Edit the network configuration file: sudo vi /etc/sysconfig/network-scripts/ifcfg-eth0 Add or modify the following lines: BOOTPROTO=none IPADDR=192.168.1.100 NETMASK=255.255.255.0 GATEWAY=192.168.1.1 DNS1=8.8.8.8 Save the file and restart the network to apply the changes: sudo systemctl restart network Verify the static IP configuration using: ip addr Disable SELinux SELinux can interfere with Kubernetes operations by blocking certain actions. Disable it temporarily with: sudo setenforce 0 To disable SELinux permanently, edit the configuration file: sudo vi /etc/selinux/config Change the line SELINUX=enforcing to SELINUX=disabled, then reboot your server for the changes to take effect. Optional: Disable the Firewall If you’re in a trusted environment, disabling the firewall can simplify setup. Run: sudo systemctl disable firewalld --now Warning: Disabling the firewall is not recommended for production environments. If you keep the firewall enabled, open ports 6443 (Kubernetes API), 10250, and 8472 (Flannel VXLAN) to ensure proper communication. Install Required Dependencies k3s doesn’t require many dependencies, but ensuring your system has tools like curl and wget installed can avoid potential errors during installation. Use: sudo yum install -y curl wget Step 2: Installing k3s With your system prepared, installing k3s is straightforward. Let’s start with the master node. Install k3s on the Master Node Run the following command to install k3s: curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE="644" sh - Pro Tip: The K3S_KUBECONFIG_MODE="644" flag makes the kubeconfig file readable by all users. This is useful for testing but not secure for production. By default, k3s sets up a single-node cluster. This is ideal for lightweight setups or testing environments. Verify Installation Confirm that k3s is running: sudo systemctl status k3s You should see a message indicating that k3s is active and running. Also, check the nodes in your cluster: kubectl get nodes Retrieve the Cluster Token To add worker nodes to your cluster, you’ll need the cluster token. Retrieve it using: sudo cat /var/lib/rancher/k3s/server/node-token Note this token—it’ll be required to join worker nodes. Install k3s on Worker Nodes On each worker node, use the following command, replacing <MASTER_IP> with your master node’s IP and <TOKEN> with the cluster token: curl -sfL https://get.k3s.io | \ K3S_URL="https://<MASTER_IP>:6443" \ K3S_TOKEN="<TOKEN>" \ sh - Verify that the worker node has successfully joined the cluster: kubectl get nodes You should see all nodes listed, including the master and any worker nodes. Step 3: Troubleshooting Common Issues Even with a simple setup, things can go wrong. Here are some common issues and how to resolve them. Firewall or SELinux Blocking Communication If worker nodes fail to join the cluster, check that required ports are open and SELinux is disabled. Use telnet to test connectivity to port 6443 on the master node: telnet <MASTER_IP> 6443 Node Not Ready If a node shows up as NotReady, check the logs for errors: sudo journalctl -u k3s Configuration Issues Misconfigured IP addresses or missing prerequisites can cause failures. Double-check your static IP, SELinux settings, and firewall rules for accuracy. Step 4: Next Steps Congratulations! You now have a functional k3s cluster on CentOS 7. Here are some suggestions for what to do next: Deploy a sample application using kubectl apply -f. Explore Helm charts to deploy popular applications like Nginx, WordPress, or Prometheus. Secure your cluster by enabling authentication and network policies. Monitor the cluster using tools like Prometheus, Grafana, or Lens. Experiment with scaling your cluster by adding more nodes. Remember, Kubernetes clusters are dynamic. Always test your setup thoroughly before deploying to production. Quick Summary k3s is a lightweight, easy-to-install Kubernetes distribution, ideal for CentOS 7. Prepare your system by updating packages, setting a static IP, and disabling SELinux. Installation is simple, but pay attention to prerequisites and firewall rules. Troubleshooting common issues like node connectivity can save hours of debugging. Explore, test, and secure your cluster to get the most out of k3s. I’m Max L, and I believe a well-configured cluster is a thing of beauty. Good luck, and happy hacking! 🛠 Recommended Resources: Tools and books mentioned in (or relevant to) this article: Kubernetes in Action, 2nd Edition — Complete K8s guide ($45-55) Docker Deep Dive — Practical Docker mastery ($30) Learning Helm — Package management for K8s ($40) 📋 Disclosure: Some links are affiliate links. If you purchase through these links, I earn a small commission at no extra cost to you. I only recommend products I have personally used or thoroughly evaluated. 📚 Related Articles How to Protect Your Homelab from Dust: A Practical Guide Fortifying Kubernetes Supply Chains with SBOM and Sigstore Scaling GitOps Securely: Best Practices for Kubernetes Secur


---
## Configure a Used Aruba S2500 Switch for Home Use

- URL: https://orthogonal.info/setup-a-used-aruba-s2500-switch-and-remove-stacking-ports/
- Date: 2022-04-03
- Category: Homelab
- Summary: Set up a used Aruba S2500 enterprise switch for home networking. Covers factory reset, stacking port removal, VLAN config, and PoE optimization tips.

Picture this scenario: You’ve just snagged a used Aruba S2500 switch for your home network—a piece of high-performance enterprise hardware at a bargain price. But as you stare at it, reality sets in: this isn’t your average consumer-grade plug-and-play device. Instead, you’re faced with a powerful yet complex piece of equipment that demands proper setup to unlock its full capabilities. Do you need to be an IT administrator to make it work? Absolutely not. Let me guide you through the process, step by step, so you can turn this switch into the backbone of your network. Why Choose Enterprise Hardware for Home Networking? 📌 TL;DR: Picture this scenario: You’ve just snagged a used Aruba S2500 switch for your home network—a piece of high-performance enterprise hardware at a bargain price. But as you stare at it, reality sets in: this isn’t your average consumer-grade plug-and-play device. 🎯 Quick Answer: Repurpose an Aruba S2500 enterprise switch for home use by factory resetting with ‘write erase all’, removing stacking configuration, assigning a management IP, then configuring VLANs and ports. Disable unused enterprise features like RADIUS and 802.1X to simplify operation. Most people rely on unmanaged switches for their home networks. They’re simple, affordable, and adequate for basic needs like streaming, browsing, and gaming. But if you’re diving into more advanced use cases—like running a home lab, setting up a 10Gbps NAS, or editing 4K video files—you’ll quickly hit the limitations of consumer-grade switches. Enterprise hardware, like the Aruba S2500, offers a cost-effective way to achieve high-speed networking without paying a premium for new consumer devices. These switches, often retired from corporate environments, deliver exceptional performance and advanced features at a fraction of the cost. For example, I purchased an Aruba S2500 48P-4SFP+ with PoE for $120 on eBay. This model provides 48 ports for devices and four 10Gbps SFP+ ports, making it perfect for demanding setups. Why does enterprise hardware outperform consumer-grade devices? It comes down to several factors: Build Quality: Enterprise devices are built for durability and reliability, often designed to operate 24/7 for years in demanding environments. Advanced Features: These switches offer features like VLANs, link aggregation, and QoS (Quality of Service), which are rare or missing in consumer switches. Scalability: Enterprise hardware can handle larger networks with higher bandwidth demands, making it ideal for future-proofing your setup. Pro Tip: When shopping for used enterprise gear, check the seller’s reviews and confirm the device is functional. Look for terms like “tested working” in the listing to avoid surprises. Step 1: Factory Reset—Starting with a Clean Slate The first step in configuring your Aruba S2500 is performing a factory reset. Used switches often come with leftover configurations from their previous environments, which could cause conflicts or undermine security. Here’s how to reset the Aruba S2500: Power on the switch and wait for it to boot up completely. Press the Menu button on the front panel to access the switch’s built-in menu. Navigate to the “Factory Reset” option using the arrow keys. Confirm the reset and wait for the switch to reboot. Once reset, the switch will revert to its default settings, including the default IP address and admin credentials. Warning: Factory reset wipes all previous configurations. Ensure you don’t need any data from the switch before proceeding. Step 2: Accessing the Management Interface After resetting the switch, you’ll need to connect to its web-based management interface. The default IP address for an Aruba S2500 is 172.16.0.254. Follow these steps to access the interface: Connect your computer to any of the Ethernet ports on the switch. Set your computer to obtain an IP address automatically via DHCP. Open your web browser and enter http://172.16.0.254 into the address bar. Log in using the default credentials: admin / admin123. If successful, you’ll see the Aruba S2500’s web interface, which allows you to configure the switch settings. Warning: If you can’t connect, ensure your computer’s IP settings match the switch’s subnet. You may need to set a static IP like 172.16.0.1 temporarily. Step 3: Securing the Switch Enterprise hardware often ships with default settings that are unsuitable for home environments. For example, the default admin password is a security risk if left unchanged. Also, your switch may be running outdated firmware, which could expose you to vulnerabilities. To secure your switch: Log into the management interface and immediately change the admin password. Assign a static IP address for easier future access. Download the latest firmware from Aruba’s support website and update the switch. Updating firmware via SSH: copy tftp://192.168.1.100/firmware.bin system:partition0 reload Replace 192.168.1.100 with your TFTP server’s IP and firmware.bin with the firmware file’s name. Pro Tip: Update both firmware partitions to ensure you have a backup in case one fails. Use copy commands for each partition. Step 4: Repurposing Stacking Ports for Regular Use The Aruba S2500 features two stacking ports designed for linking multiple switches in a stack. In a home setup, these are often unnecessary and can be repurposed for standard network traffic. To repurpose the stacking ports: Connect to the switch via SSH using tools like PuTTY or the terminal. Enter enable mode by typing en and providing your enable password. Remove the stacking interfaces with the following commands: delete stacking interface stack 1/2 delete stacking interface stack 1/3 After executing these commands, the stacking ports will function as regular SFP+ ports capable of 10Gbps speeds. Save your configuration and reboot the switch for changes to take effect. Warning: Always save your configuration before rebooting. Unsaved changes will be lost. Step 5: Testing and Optimizing Your Setup With the switch configured, it’s time to test your setup to ensure everything is working as expected. Connect devices to the switch and verify network communication and performance. To test bandwidth between devices, use iperf. Here’s an example: iperf3 -c 192.168.1.50 -P 4 Replace 192.168.1.50 with the IP address of the target device. This command tests bandwidth using four parallel streams. Pro Tip: Use VLANs to segment your network and prioritize traffic for specific devices like servers or NAS units. Troubleshooting Common Pitfalls Even with careful setup, you may encounter issues. Here are some common problems and solutions: Can’t access the web interface: Verify your computer’s IP settings and check if the switch’s IP matches its default 172.16.0.254. Firmware update fails: Ensure your TFTP server is running and the firmware file is correctly named. Stacking ports remain inactive: Reboot the switch after repurposing the ports to finalize changes. Advanced Features to Explore Once your Aruba S2500 is up and running, you can dive deeper into its advanced features: VLAN Configuration: Create virtual LANs to segment your network for better organization and security. QoS (Quality of Service): Prioritize certain types of traffic, such as video calls or gaming, to ensure smooth performance. Link Aggregation: Combine multiple physical links into a single logical link for increased bandwidth and redundancy. Quick Summary Used enterprise switches like the Aruba S2500 offer high performance at a fraction of the cost. Factory reset and firmware updates are essential for both functionality and security. Repurposing stacking ports unlocks additional 10Gbps connectivity. Testing and optimizing your setup ensures smooth operation and peak performance. Advanced features like VLANs, QoS, and link aggregation allow you to customize your network to meet your needs. With the right approach, configuring the Aruba S2500 doesn’t have to be daunting. Follow these steps, and yo


---
## Async to Promise Conversion in JavaScript Guide

- URL: https://orthogonal.info/how-to-convert-an-async-function-to-promise-in-javascript/
- Date: 2022-03-06
- Category: JavaScript
- Summary: Convert async/await functions to Promises in JavaScript. Covers patterns, error handling, chaining, and when to use each approach in real-world projects.

Why Might You Need to Convert an Async Function to a Promise? 📌 TL;DR: Why Might You Need to Convert an Async Function to a Promise? Imagine this: you’re knee-deep in developing a sophisticated JavaScript application. Your codebase is modern, Using async/await for clean and readable asynchronous flows. 🎯 Quick Answer Why Might You Need to Convert an Async Function to a Promise? Imagine this: you’re knee-deep in developing a sophisticated JavaScript application. Your codebase is modern, Using async/await for clean and readable asynchronous flows. Imagine this: you’re knee-deep in developing a sophisticated JavaScript application. Your codebase is modern, Using async/await for clean and readable asynchronous flows. Suddenly, you need to integrate with a legacy library that only understands Promises. What do you do? This scenario isn’t uncommon. Despite async functions being built on Promises, there are situations where explicit control over the Promise lifecycle becomes critical. Here are a few real-world examples: Interfacing with frameworks or tools that don’t support async/await. Adding retries, logging, or timeouts to async functions. Debugging complex asynchronous workflows with granular control. I’ll walk you through everything you need to know about converting async functions to Promises, along with practical techniques, troubleshooting advice, and pro tips. Let’s dive in. Understanding Async Functions and Promises Before jumping into conversions, it’s essential to understand the relationship between async functions and Promises at a deeper level. Async Functions Demystified Async functions were introduced in ES2017 and revolutionized how we write asynchronous JavaScript code. They allow us to write asynchronous logic in a way that resembles synchronous code. Here’s a quick example: async function fetchData() { const response = await fetch('https://api.example.com/data'); const data = await response.json(); return data; } fetchData() .then(data => console.log('Data:', data)) .catch(error => console.error('Error:', error)); In this snippet, the await keyword pauses the execution of fetchData() until the Promise returned by fetch() is resolved. The function itself returns a Promise that resolves with the parsed JSON data. Promises: The Foundation of Async Functions Promises are the building blocks of async functions. They represent an operation that may complete in the future, and they have three states: Pending: The operation hasn’t completed yet. Fulfilled: The operation succeeded. Rejected: The operation failed. Here’s a basic example of working with Promises: const delay = new Promise((resolve, reject) => { setTimeout(() => resolve('Done!'), 2000); }); delay .then(message => console.log(message)) // Logs "Done!" after 2 seconds .catch(error => console.error(error)); Async functions are essentially syntactic sugar over Promises, making asynchronous code more readable and intuitive. How to Convert an Async Function to a Promise Converting an async function to a Promise is straightforward. You wrap the async function in the new Promise constructor. Here’s the basic pattern: async function asyncFunction() { return 'Result'; } const promise = new Promise((resolve, reject) => { asyncFunction() .then(result => resolve(result)) .catch(error => reject(error)); }); Here’s what’s happening: asyncFunction is executed within the Promise constructor. The then method resolves the Promise with the result of the async function. The catch method rejects the Promise if the async function throws an error. Practical Example: Adding a Retry Mechanism Let’s create a wrapper around an async function to add retries: async function fetchData() { const response = await fetch('https://api.example.com/data'); if (!response.ok) { throw new Error('Failed to fetch data'); } return await response.json(); } function fetchWithRetry(retries) { return new Promise((resolve, reject) => { const attempt = () => { fetchData() .then(data => resolve(data)) .catch(error => { if (retries === 0) { reject(error); } else { retries--; attempt(); } }); }; attempt(); }); } fetchWithRetry(3) .then(data => console.log('Data:', data)) .catch(error => console.error('Error:', error)); Pro Tip: Use exponential backoff for retries to avoid hammering APIs unnecessarily. For example, increase the wait time between retries exponentially. Practical Example: Logging Async Function Results Sometimes, you might want to log the results of an async function without modifying its core logic. Wrapping it in a Promise is one way to achieve this: async function fetchData() { const response = await fetch('https://api.example.com/data'); return await response.json(); } function fetchWithLogging() { return new Promise((resolve, reject) => { fetchData() .then(result => { console.log('Fetched data:', result); resolve(result); }) .catch(error => { console.error('Fetch failed:', error); reject(error); }); }); } fetchWithLogging() .then(data => console.log('Data:', data)) .catch(error => console.error('Error:', error)); Timeouts: A Common Use Case Timeouts are a frequent requirement in asynchronous workflows. They allow you to ensure that a task doesn’t hang indefinitely. Async functions don’t natively support timeouts, but you can implement them using Promises: function withTimeout(asyncFunction, timeout) { return new Promise((resolve, reject) => { const timer = setTimeout(() => reject(new Error('Timeout exceeded')), timeout); asyncFunction() .then(result => { clearTimeout(timer); resolve(result); }) .catch(error => { clearTimeout(timer); reject(error); }); }); } async function fetchData() { const response = await fetch('https://api.example.com/data'); return response.json(); } withTimeout(fetchData, 5000) .then(data => console.log(data)) .catch(error => console.error(error)); Pro Tip: Use timeouts to prevent your application from hanging indefinitely during network requests. Common Pitfalls and Troubleshooting While converting async functions to Promises is handy, it’s not without risks. Let’s address common pitfalls: Redundant Wrapping Async functions already return Promises, so wrapping them unnecessarily adds complexity: // Avoid this const promise = new Promise((resolve, reject) => { asyncFunction() .then(result => resolve(result)) .catch(error => reject(error)); }); // Prefer this const promise = asyncFunction(); Warning: Only wrap async functions when you need additional control, such as retries or timeouts. Unhandled Rejections Promises can fail silently if errors are not handled: async function fetchData() { const response = await fetch('https://api.example.com/data'); return response.json(); // Potential error if response isn’t valid } // Forgetting error handling fetchData(); Always use .catch() or try/catch blocks to handle errors: fetchData() .then(data => console.log(data)) .catch(error => console.error(error)); Performance Overhead Wrapping async functions in Promises can introduce slight performance overhead, especially in scenarios with frequent asynchronous calls. Optimize the usage of this pattern in performance-critical code. Advanced Techniques Combining Multiple Async Functions with Promise.all When working with multiple async functions, you can use Promise.all to execute them concurrently and wait for all of them to complete: async function fetchData1() { return await fetch('https://api.example.com/data1').then(res => res.json()); } async function fetchData2() { return await fetch('https://api.example.com/data2').then(res => res.json()); } function fetchBoth() { return Promise.all([fetchData1(), fetchData2()]); } fetchBoth() .then(([data1, data2]) => { console.log('Data1:', data1); console.log('Data2:', data2); }) .catch(error => console.error('Error:', error)); This technique is particularly useful when you need to fetch data from multiple sources simultaneously. Quick Summary Async functions inherently return Promises, but wrapping them can provide additional control. Use new Promise to impleme

Also by us: StartCaaS — AI Company OS · Hype2You — AI Tech Trends