Redis Connection Refused: Diagnose and Fix in 5 Steps

Redis Connection Refused: Diagnose and Fix in 5 Steps

Error: Redis connection to 127.0.0.1:6379 failed - connect ECONNREFUSED 127.0.0.1:6379

Your caching layer is down, your app is throwing errors, and you need to fix it fast.

Here’s the systematic way to diagnose and resolve Redis connection failures.

Step 1: Is Redis Actually Running?

# Check if Redis process exists
ps aux | grep redis

# Check systemd service status
sudo systemctl status redis
sudo systemctl status redis-server  # Ubuntu/Debian

# Check if it's listening on the port
ss -tlnp | grep 6379

If Redis is stopped:

sudo systemctl start redis-server
sudo systemctl enable redis-server  # Auto-start on boot

Step 2: Test the Connection Directly

# Basic ping test
redis-cli ping
# Expected: PONG

# If using a password (requirepass)
redis-cli -a your_password ping

# If Redis is on a different host
redis-cli -h your-redis-host -p 6379 ping

If redis-cli works but your app can’t connect — the problem is in your app’s connection config, not Redis itself.

Step 3: Check Redis Bind Configuration

A common issue: Redis is running but only accepting connections from localhost.

# Check current bind config
grep -i "^bind" /etc/redis/redis.conf

Default output:

bind 127.0.0.1 -::1

This means Redis only accepts connections from 127.0.0.1. If your app is in a Docker container, it can’t reach this address.

Fix for Docker:

# Option 1: Bind to all interfaces (careful in production)
# In /etc/redis/redis.conf:
bind 0.0.0.0

# Option 2: Use host.docker.internal (Mac/Windows Docker Desktop)
# In your app:
REDIS_HOST=host.docker.internal

# Option 3: Docker Compose (best practice)
# Put Redis and app in the same Docker network, use service name
REDIS_HOST=redis  # the service name in docker-compose.yml

Step 4: Check Redis Memory (Prevent Future Failures)

Redis can crash or refuse writes if it runs out of memory:

redis-cli info memory | grep -E "used_memory_human|maxmemory_human|maxmemory_policy"
used_memory_human:1.23G
maxmemory_human:1.00G        # Uh oh — used > max
maxmemory_policy:noeviction  # Refuses writes when full

Fix:

# Set a sensible eviction policy
redis-cli CONFIG SET maxmemory-policy allkeys-lru

# Increase memory limit
redis-cli CONFIG SET maxmemory 2gb

# Persist config changes
redis-cli CONFIG REWRITE

Step 5: Check Firewall Rules

# Is port 6379 open?
sudo ufw status | grep 6379

# If you need external access (be careful)
sudo ufw allow from your-app-server-ip to any port 6379

# Check iptables if not using ufw
sudo iptables -L -n | grep 6379

Security note: Never expose Redis port 6379 to the open internet. Always restrict by IP or use a private network.

Quick Connection Test Script

#!/bin/bash
HOST=${REDIS_HOST:-127.0.0.1}
PORT=${REDIS_PORT:-6379}

echo "Testing Redis at $HOST:$PORT..."

# Test TCP connection
nc -zv $HOST $PORT 2>&1 && echo "TCP: OK" || echo "TCP: FAILED"

# Test Redis response
redis-cli -h $HOST -p $PORT ping && echo "Redis: OK" || echo "Redis: FAILED"

I built ARIA to solve exactly this.
Try it free at step2dev.com — no credit card needed.

Building a Zero-Dependency Random String Generator for Node.js (With Secure Mode)

Generating random strings is one of those things every backend developer ends up doing — IDs, tokens, session keys, you name it.

But while working on this, I realized most implementations fall into a few common traps:

  • Using Math.random() everywhere (not secure)
  • Inefficient “generate + filter” logic
  • Pulling heavy dependencies for a simple problem

So I decided to build a clean solution from scratch — and learned a few interesting things along the way.

❌ The Common Mistake

A lot of implementations generate from a full alphanumeric charset and then filter results using a regex check inside the loop — only keeping characters that match the desired type. This means:

Problems:

  • Wastes iterations (filtering)
  • Uses regex inside loops
  • Performance depends on randomness
  • Hard to maintain

✅ A Better Approach

Instead of generating and filtering — generate directly from the correct character set. Pre-define separate charsets for alpha, numeric, and alphanumeric, then index into the right one. This eliminates filtering entirely and keeps every iteration productive.

🔐 Adding a Secure Layer

For things like API keys, auth tokens, and session IDs — Math.random() is not enough.

So I implemented a secure version using the Web Crypto API (crypto.getRandomValues), with rejection sampling to eliminate modulo bias — ensuring every character in the charset has a perfectly uniform probability of being chosen.

🧠 Why This Matters

🔹 Cryptographic randomness
Uses OS-level entropy instead of pseudo-random numbers.

🔹 No modulo bias
Ensures uniform distribution of characters.

🔹 Predictable performance
O(n) complexity with no wasted iterations.

⚡ Final API Design

I wrapped everything into a simple interface:

generateCode({ length: 12 });
generateCode({ length: 12, type: "alpha" });
generateCode({ length: 12, secure: true });

📦 The Result

I packaged this into a small utility: genkode — random string & ID generator (zero dependency).

import { generateCode } from "genkode";

generateCode({ length: 10 });
// "a8KxP2LmQz"

👉 https://www.npmjs.com/package/genkode

💡 Key Takeaways

  • Avoid generate + filter patterns
  • Use the correct character domain upfront
  • Use crypto for anything security-related
  • Keep utilities simple and dependency-free

If you’ve tackled this differently or have suggestions, I’d love to hear them 👇

OAuth 2.0 Explained: From Authorization Codes to PKCE (The Complete Picture)

OAuth is everywhere and most developers use it without really understanding what’s happening under the hood. You click “Sign in with Google,” magic happens, and you’re logged in. But when something breaks — a token expires, a redirect fails, a scope is wrong — you’re suddenly debugging a protocol you never learned.

I built OAuth integrations for years before I actually understood the full flow. Here’s what I wish someone had explained from the start.

The Four Actors

OAuth has four players, and mixing them up is where most confusion starts:

  • Resource Owner — that’s you, the user. You own the data.
  • Client — the app requesting access to your data. Could be a web app, mobile app, or CLI tool.
  • Authorization Server — issues tokens after you grant permission. Google, GitHub, Auth0 — these run authorization servers.
  • Resource Server — the API that holds your actual data. Sometimes this is the same company as the auth server, sometimes not.

The whole point of OAuth: the Client gets limited access to the Resource Server without ever seeing your password.

The Authorization Code Flow (Step by Step)

This is the most common OAuth flow, and the one you should use for almost everything:

1. The app redirects you to the authorization server. The URL includes the client ID, requested scopes (what permissions it wants), a redirect URI (where to send you back), and a state parameter (CSRF protection).

2. You log in and consent. The authorization server shows you what the app is asking for — “Acme App wants to read your email and profile.” You say yes or no.

3. The auth server redirects back with a code. Not a token — a short-lived authorization code. This is a critical distinction. The code is useless on its own.

4. The app exchanges the code for tokens. This happens server-to-server (backend to auth server), using the authorization code plus the client secret. The response includes an access token and usually a refresh token.

5. The app uses the access token to call the API. The token goes in the Authorization header. The resource server validates it and returns data.

Why not just return the token directly in step 3? Because the redirect happens in the browser — it’s visible in the URL bar, browser history, and server logs. The code-for-token exchange happens server-side where it’s actually secure.

PKCE: Fixing the Gap for Public Clients

The authorization code flow has a weakness: what if the client can’t keep a secret? Mobile apps and single-page apps ship their entire source code to the user. There’s no safe place to store a client secret.

PKCE (pronounced “pixie”) — Proof Key for Code Exchange — fixes this. Here’s how:

Before the auth request, the client generates a random string called a code_verifier and computes its SHA-256 hash, called the code_challenge.

During the auth request, the client sends the code_challenge to the authorization server.

During the token exchange, the client sends the original code_verifier. The auth server hashes it and checks that it matches the challenge from earlier.

An attacker who intercepts the authorization code can’t use it — they don’t have the original code_verifier. The math is one-directional: you can go from verifier to challenge, but not back.

OAuth 2.1 makes PKCE mandatory for all clients, not just public ones. That tells you how important the security community considers it.

Access Tokens vs Refresh Tokens

Access tokens are short-lived (minutes to hours) and used for API calls. When they expire, the app uses the refresh token to get a new one without making the user log in again.

Refresh tokens are long-lived and powerful — losing one is like losing a session. Best practice: rotate refresh tokens on every use (one-time-use tokens) and store them securely.

OAuth vs OpenID Connect

OAuth handles authorization — “can this app access my photos?” OpenID Connect (OIDC) adds authentication on top — “who is this user?” OIDC introduces the ID token, which contains user identity claims (name, email, etc.) in a JWT format.

If you need “Sign in with X” — that’s OIDC. If you need “let this app read my calendar” — that’s OAuth. In practice, most implementations use both together.

Common Mistakes I’ve Seen

Storing tokens in localStorage. Accessible to any JavaScript on the page, including XSS attacks. Use httpOnly cookies or in-memory storage instead.

Overly broad scopes. Request the minimum permissions you need. If you only read emails, don’t ask for write access.

Not validating the state parameter. This is your CSRF protection. Skip it and attackers can trick users into linking their account to the attacker’s OAuth session.

Treating access tokens as proof of identity. An access token says “this request is authorized.” It doesn’t reliably tell you who made it. Use OIDC ID tokens for identity.

The Bottom Line

OAuth looks complicated because it solves a genuinely hard problem: delegated access without shared credentials, across untrusted networks, for multiple client types. Once you see the reasoning behind each step — why there’s a code exchange, why PKCE exists, why tokens are split into access and refresh — the protocol makes a lot of sense.

The video above walks through each step visually. If you’re implementing OAuth from scratch, I’d recommend watching it alongside the official RFC 6749 and Auth0’s docs. Theory plus implementation examples is the fastest way to get this stuff to click.

Building something with OAuth right now? Hit me up in the comments — happy to help debug.

How to Use Ollama From Your Android Phone in 2026 (Private, Local AI)

Running powerful AI models locally on your computer is no longer the hard part in 2026. The real challenge used to be access—how do you actually use that power from the device you carry all day?

There’s still no official mobile app for LM Studio. And for a while, the unofficial solutions were messy: reverse proxies, manual IP configs, separate clients, and constant connection issues.

Now there’s a much cleaner approach.

With LMSA, you can connect your Android phone directly to models running on your own machine—including those served via Ollama or LM Studio—without complicated setup.

This turns your local AI into something actually usable, portable, and private.

👉 https://lmsa.app

Why This Matters (Local AI in 2026)

Local AI has improved dramatically over the past year.

Models like Qwen 3.5 9B now deliver performance that, for everyday use, rivals many paid cloud tools—while running entirely on consumer hardware.

That means:

  • No monthly subscriptions
  • No sending data to external servers
  • No latency from cloud calls
  • Full control over your data

The only missing piece was a clean mobile interface.

That’s exactly what LMSA provides.

Step-by-Step: Connect LMSA to LM Studio

This setup takes only a few minutes and works entirely over your local network.

1. Install LMSA on Your Android Phone

  • Download and open LMSA
  • Tap “Start Here” to begin setup

This guided flow is designed to get you connected without needing technical knowledge.

2. Open Server Configuration

  • Tap “Configure” next to Server

You’ll enter your computer’s connection details here shortly.

3. Start Your Local AI Server (Desktop)

On your computer:

  1. Open LM Studio
  2. Navigate to the Server / Developer tab
  3. Load a model
  4. Click Start Server
  5. Enable:
  • CORS
  • Network Access

These settings allow external devices (like your phone) to securely connect.

4. Connect Your Phone to Your PC

Back on your Android device:

  • Find your computer’s local IP address (example: 192.168.1.12)
  • Enter it into LMSA
  • Tap Apply

As long as both devices are on the same Wi-Fi network, the connection should work instantly.

👉 https://lmsa.app

5. Load and Run a Model

  • Tap Models inside LMSA
  • Select the model exposed by your server
  • Tap Load

Within seconds, your phone is now connected to your desktop model.

6. Start Chatting

That’s it.

You now have a fully local AI assistant running on your PC—but accessible from your phone.

No cloud. No accounts. No external dependencies.

What LMSA Does Better Than a Web UI

You can connect to a local server through a browser—but that’s a very limited experience.

LMSA is built as a dedicated AI environment, which unlocks much more:

1. Model Management in One Place

Switch between multiple models without restarting your workflow. Use smaller models for speed and larger ones for deeper reasoning.

2. Persistent Chat Experience

Your conversations are structured and saved in a way that feels like a real assistant—not just a temporary session.

3. Mobile-First Design

Everything is optimized for touch, responsiveness, and real-world use—not desktop web layouts squeezed onto a phone.

4. Fully Private by Design

All communication stays on your local network. Your data never leaves your devices.

Recommended Models for Local Use

If you’re unsure where to start:

  • Qwen 3.5 9B → Best balance of speed and quality
  • Smaller 2B–4B models → Faster, good for lightweight tasks
  • Larger models → Better reasoning, require more RAM/GPU

When paired with LM Studio or Ollama, these models become surprisingly powerful for daily use.

You Already Own the Setup

One of the biggest advantages of this approach is that you don’t need anything new:

  • Your desktop or laptop runs the model
  • Your Android phone becomes the interface
  • Your Wi-Fi network connects them

That’s it.

No paid APIs. No extra infrastructure. No cloud lock-in.

The Bigger Picture: Personal AI Systems

Tools like LMSA are part of a bigger shift toward personal AI ecosystems:

  • Your devices working together
  • Local-first intelligence
  • Shared context across environments
  • Complete user control

Instead of relying on centralized AI services, you’re building your own.

Quick Setup Recap

  1. Install LMSA
  2. Tap Start Here → Configure Server
  3. Start server in LM Studio
  4. Enable CORS + Network Access
  5. Enter your PC’s IP → Apply
  6. Load a model → Chat

Final Thoughts

Local AI is no longer experimental—it’s practical.

With tools like LMSA, the gap between desktop AI and mobile usability has effectively disappeared.

What used to take hours of setup now takes minutes.

And once it’s running, you have something powerful:

A private, fast, fully offline-capable AI system that lives on your devices—and works wherever you are.

Try It Now

If you want a fast, private way to use your local AI models from your phone, LMSA is the simplest solution available right now.

👉 https://lmsa.app

Your local AI is no longer stuck on your desktop—it’s now in your pocket.

Before Writing a Single Line of Code — Planning My Gym App🏋️

Gym schedule sequence diagram

Hey fellow devs, 👋

Moving on from AI chaos, this time it’s all about learning something new.

💡 What I’m Building

I’m building a simple gym schedule app — entirely with the help of Claude.

The app lets users set a weekly schedule from Monday to Sunday, with their gym
timings, workout split, exercises, reps and sets. Nothing fancy — this is purely
a practice project. But the plan is to build it right, not just fast.

Since vibe coding is clearly the future, getting comfortable with it early felt
like a smart move. 🚀

📊 Sequence Diagrams — What Even Are They?

A sequence diagram maps out how different parts of a system interact with each
other in a specific order over time.

Think of it like a WhatsApp conversation — you can see exactly who said what, to whom, and in what order.

Here’s the sequence diagram I made for my app:

Gym schedule sequence diagram

🧠 How I Actually Built It

The first step is identifying your actors — the objects that interact with
the system. For my app this was straightforward:

  • The User — inputs the schedule
  • The UI — handles what the user sees and does
  • The Database — saves and retrieves the data

Simple enough. But here’s where it gets interesting.

Good developers don’t just map out the ideal scenario. They think about
everything that could go wrong. Because it’s an imperfect world.

For example — what happens if the user tries to save a workout day without
filling in all the fields? No exercises, no reps, just an empty entry hitting
the database. The diagram needs to account for that validation step, otherwise
that’s a bug waiting to happen from day one.

Thinking about failure early is what separates planned apps from messy ones.

🗺️ Why Planning Actually Matters

Jumping straight into code is tempting. I get it.

But mapping the flow first forces you to ask questions you’d otherwise miss —
Is this feature actually necessary? What happens if this step fails? Does this
even make sense?

In a small project like this it keeps things clean. In larger projects, it can
save weeks of rework. The planning stage is unglamorous but it’s genuinely
important.

🤖 Claude’s Role — Plan Mode

Claude is brilliant at building apps, but even Claude isn’t perfect — and
usually when something goes wrong, it’s because the prompt wasn’t clear enough.
That’s on the user, not Claude.

So before touching any code, I sent Claude my sequence diagram and we
brainstormed the entire app structure together in plan mode — no building,
just thinking. Getting Claude to deeply understand what I want before it starts
writing anything makes the whole process cleaner and faster.

Context first, code later. ✨

Next up — enough planning, time to actually build something. See you there. 👀

90% of Features Fail — Your Team Probably Isn’t Asking This One Question

I’ve watched teams burn months building features nobody wanted. Brilliant engineers, solid code, clean architecture — shipped to absolute silence. Zero adoption. The feature just… sits there.

The failure rate is brutal. According to MIT, roughly 95% of new products miss the mark. Pendo’s research shows that 80% of features in the average SaaS product are rarely or never used. That’s not a rounding error. That’s a systemic problem.

So what’s going wrong?

The “Why” Problem

Most teams skip the most basic question: why would someone actually use this?

Not “would this be cool?” Not “did a customer ask for it?” Not “does our competitor have it?” The real question is deeper — what job is this feature doing for someone, and is that job painful enough that they’ll change their behavior to use it?

A CPO I respect put it this way: ship when you know the “why,” not when the code is done. That sounds obvious. In practice, almost nobody does it.

The Feature Factory Trap

Here’s how it usually goes. Product gets a request from sales. Engineering estimates it at two sprints. Leadership wants it for the quarterly roadmap. Nobody stops to validate whether the 200 users who’d benefit will actually adopt it.

Two months later, usage data comes in: 3% adoption. The feature gets quietly buried in a submenu. The team moves on to the next thing.

Pendo found that the average SaaS app has features that cost $2M+ to build sitting completely unused. Multiply that across an org’s entire product surface and you’re looking at staggering waste.

What Actually Works

The teams that beat the 90% failure rate share a few habits:

They talk to users before writing code. Not surveys — actual conversations. Five interviews can reveal more than 5,000 survey responses because you hear the hesitation, the workarounds, the “well, what I actually do is…” moments.

They define success criteria upfront. “This feature succeeds if 30% of power users adopt it within 60 days.” Without that, every feature is simultaneously a success and a failure because nobody agreed on what winning looks like.

They kill features. This is the hard one. Removing a feature that took three months to build feels wasteful. Maintaining a feature nobody uses is the actual waste — it adds complexity, slows down the codebase, and confuses new users.

They measure behavior, not opinions. Users will tell you they want something in a survey, then never use it. Track what people do, not what they say they’ll do.

The CPO Mindset

The best Chief Product Officers I’ve seen operate with a simple filter: every feature needs a clear user problem, a measurable outcome, and a kill criteria. If you can’t articulate all three in a sentence each, the feature isn’t ready for engineering.

This isn’t about being slow. It’s about being deliberate. Teams that validate before building actually ship faster because they waste less time on things that don’t matter.

The Uncomfortable Truth

Most features fail because building is easier than thinking. Writing code feels productive. Running user interviews feels slow. But the math is clear — a week of validation saves months of wasted development.

Next time your team is about to build something, ask: “If we shipped this tomorrow, who specifically would use it, and what would they stop doing instead?” If you can’t answer that with names and specifics, you’re probably about to join the 90%.

What’s the worst feature your team shipped that nobody used? I’m genuinely curious — drop it in the comments.

Web App Pen Test: What I Check in the First 10 Minutes of Every Engagement

TLDR: Most people imagine pen testing as a montage of terminals, complex exploits, and hours of deep technical work. The reality is that the first 10 minutes are almost always the most revealing. I run the same opening checklist on every web application I assess — and in those 10 minutes, I almost always find 2 or 3 things that a real attacker would exploit before they even get to the sophisticated stuff. Here’s exactly what that checklist looks like, and how you can run it on your own application today.

Why the First 10 Minutes Tell You So Much

There’s a principle in security that’s uncomfortable but consistently true: the most dangerous vulnerabilities in your application are usually the obvious ones. Not because your team is careless — but because obvious things are easy to miss when you’re deep in feature development, operating under deadline pressure, and focused on what your application does rather than what it shouldn’t allow.

An attacker approaching your application cold has no context, no assumptions, and no attachment. They look at the surface before they try to break through it. They check what you’ve accidentally left visible before they try to find what’s deliberately hidden.

That’s exactly how I start every assessment. No tools running yet. No automated scans. Just a browser and a clear mental checklist.

Here’s what’s on it.

The 9-Point Opening Checklist

1. HTTP to HTTPS Enforcement and Cookie Security

First thing I do: type http:// (not https) in front of the domain. Does the application redirect? Does it redirect with a 301 (permanent) or a 302 (temporary)? Is HTTP Strict Transport Security (HSTS) set in the response headers?

Then I log in and open DevTools. I look at every cookie the application sets. Three questions: Is the Secure flag set (cookie only transmitted over HTTPS)? Is HttpOnly set (JavaScript can’t read it)? Is SameSite configured to Strict or Lax?

A session cookie missing any of these flags is a vulnerability I’ll document in every report. It’s also one of the simplest fixes in existence.

2. Security Response Headers

Ctrl+Shift+I. Network tab. I load the application and look at the response headers on the main document. Six headers tell me an enormous amount in under 60 seconds:

  • Content-Security-Policy — absent or set to * means XSS mitigations are wide open
  • X-Frame-Options or frame-ancestors CSP — absent means clickjacking is possible
  • X-Content-Type-Options: nosniff — absent means MIME-type sniffing attacks are viable
  • Referrer-Policy — absent means sensitive URLs in the referer header leak to third parties
  • Permissions-Policy — reveals what browser APIs the application uses
  • Server and X-Powered-By — if these are present, they’re telling me your web server version and framework. That’s free reconnaissance I didn’t have to work for.

Missing security headers are a quick win for attackers and a quick fix for developers. They’re also almost always present in the findings of every assessment I run.

3. robots.txt and sitemap.xml

Every pen tester checks these. Attackers do too — it takes three seconds.

/robots.txt was designed to tell search engines which paths not to index. It’s essentially a publicly available map of paths you consider sensitive. I’ve found admin panels, internal API endpoints, staging directories, and backup locations all listed in robots.txt files on production applications.

/sitemap.xml gives me a complete list of every URL the application wants indexed. It tells me the full scope of the application before I’ve done any discovery work myself.

Neither of these is a vulnerability by itself. But both reliably point me toward the most interesting parts of the application within the first two minutes.

4. IDOR Check on Every Visible ID Parameter

The moment I see a URL like /account/profile?id=1042 or /invoice/download?ref=8834, I open a second browser, log in as a different user, and try to access those same URLs.

If I get the first user’s data in the second user’s session — that’s an IDOR. Full stop. This is Broken Access Control, #1 on OWASP 2025 for the fifth consecutive year, and it’s how 10 million Optus customer records were stolen by incrementing a single integer.

I also check whether IDs are sequential integers. If they are, even a fully authenticated endpoint is at higher risk — because enumeration doesn’t require guessing. We covered this pattern in depth in this post on IDOR vulnerabilities.

5. JavaScript File Review

Modern web applications ship enormous JavaScript bundles to the browser. The developer’s intent is to send the frontend code. What often comes along for the ride: internal API endpoint paths, environment variable names, hardcoded API keys, commented-out debug code, and internal service URLs that were never meant to be public.

I open the browser’s Sources tab, look through the loaded JS files, and run a quick search for strings like api_key, secret, token, internal, admin, and TODO. You would be surprised how often this surfaces something useful in under five minutes. We’ve written about what happens when secrets make it into code — the same pattern applies to client-side JavaScript.

6. Password Reset Flow

Authentication flows are where I spend serious time later in an assessment, but the password reset flow gets a quick check early because it fails so consistently. I specifically look for: Does the reset token expire? Can I reuse a token after it’s been used once? Is there a rate limit on reset requests, or can I flood the endpoint? Is the token short enough to brute-force?

The weakest reset flows I’ve seen use six-digit numeric tokens with no expiry and no rate limiting. That’s 1,000,000 possible combinations and unlimited attempts — a brute-force that takes minutes. We covered why broken authentication shows up this consistently and what a secure implementation looks like.

7. Error Messages and Information Disclosure

I start poking at inputs with values they weren’t designed to handle. A single quote in a search field. A letter in a numeric ID field. A negative number in a quantity field. An oversized string in a text input.

What I’m looking for is what the application says when it breaks. Does it return a generic “something went wrong” message, or does it return a stack trace showing me your framework version, your file paths, your database schema, and your internal IP addresses?

Verbose error messages are free reconnaissance for an attacker. They’re also a misconfiguration finding that belongs in every report because it directly accelerates every other attack.

8. Subdomain Enumeration

Still in that first 10 minutes, I’ll run a quick passive subdomain check. Tools like SecurityTrails, crt.sh (certificate transparency logs), and DNSdumpster surface subdomains without sending a single packet to the target. I’m looking for: staging environments, old API versions, admin panels, internal tools, and forgotten development servers.

The Internet Archive breach we covered here started with a forgotten development subdomain. This is not an edge case — it’s one of the most reliable findings on every assessment I run.

9. Unauthenticated LLM and AI Endpoints (The 2026 Addition)

This one didn’t exist on my checklist two years ago. Now it’s standard.

If I can tell from the application’s functionality or JavaScript that it’s using an LLM backend — a chat feature, an AI assistant, a document summarisation tool — I immediately look for the API endpoint that talks to it. I check whether it’s authenticated. I check whether I can call it directly without a user session. I check whether it has rate limiting. I check whether it’s proxied through the application’s own backend or hitting OpenAI/Anthropic directly with a hardcoded key in the client-side JavaScript.

Unauthenticated LLM endpoints are how LLMjacking attacks happen. And the API security blind spots that affect standard endpoints are even more prevalent in AI feature implementations because they’re often built quickly by teams without a security background.

What These Findings Tell Me About the Rest of the Engagement

Here’s the part that matters most for engineering leaders: when I find issues in these first 10 minutes, it’s not because I’ve found the edge cases. It’s because I’ve found the surface layer. These are the things a competent attacker finds in their first pass before they’ve even started trying.

If an application fails on three or four of these checks, it tells me the rest of the assessment is going to be thorough. It suggests that security wasn’t a structured part of the build process — that it was assumed to be handled rather than explicitly designed in.

If an application passes most of these cleanly, I know I’m working with a team that has thought about security at the implementation level. The assessment gets more interesting from there — we start finding the architectural and logic issues that take more effort — but the low-hanging fruit is gone.

The full scope of what we test beyond this is covered in our complete web app pen test checklist. The first 10 minutes are just the opening conversation.

How to Run This on Your Own App Right Now

You don’t need any specialist tools for most of this checklist. A browser with DevTools open, a second test account, and the URLs crt.sh, dnsdumpster.com, and your own application are enough to cover roughly half of it in under 30 minutes.

Open your application. Work through each of the nine points above. Write down what you find. If anything flags — a missing security header, an IDOR that works across accounts, a subdomain you’d forgotten about — that’s something worth addressing before you let a real attacker find it.

How Kuboid Secure Layer Can Help

The first 10 minutes are a free check you can run yourself. What comes after requires a structured methodology, adversarial thinking, and experience with what the findings in the surface layer usually lead to underneath.

Our web application penetration tests start with this checklist and go significantly further — covering authentication logic, business logic flaws, server-side vulnerabilities, and the full OWASP Top 10:2025 framework. If you’d like to see exactly what we look for across a full engagement, book a free consultation and we’ll walk you through our process.

If you’ve ever run a quick security check on your own application — even informally — what did you find? I’m genuinely curious. Drop a comment below. The most common answer is “more than I expected.”

If you’re building APIs or working on production systems, this guide will help you avoid common security mistakes. Watch the full tutorial here: https://youtu.be/bNi8wfrPRjM Let me know your thoughts or questions in the comments

saint_vandora profile

Compliance Without Comprehension

Here is a checklist item from a prompt I wrote three weeks ago:

✅ Ensure formulas are appropriate for student level

Here is what replaced it:

If you remove this formula, would the student lose understanding that the narration alone can’t provide? If yes, keep it. If no, remove it.

The output quality, scored by an independent evaluator, went from 2.8 to 4.3 out of 5.

The checklist version is an instruction. Follow it. Check the box. Move on.

The replacement is a question. And not any question — a question you can’t answer without thinking. You have to consider the formula, the student, the narration, and the relationship between all three. Only then can you answer yes or no.

The checklist asks for compliance. The question demands comprehension.

I’ve been building an educational video pipeline where an LLM generates lesson scripts. For weeks, I used checklists to control quality: check formula density, check vocabulary level, check engagement hooks, check adaptation to student persona.

The LLM checked every box. And the output was mediocre.

Not wrong — mediocre. Like a student who answers every question on the worksheet but hasn’t learned anything. The formulas were “appropriate for student level” in the sense that nothing was obviously wrong. But they were generic, safe, interchangeable. The narration matched the vocabulary ceiling without matching the student. Everything was correct and nothing was good.

Compliance without comprehension.

The insight came from noticing what the LLM didn’t do. When told “ensure formulas are appropriate,” it scanned for obviously wrong formulas and left everything else. It found a path to compliance that didn’t require understanding.

This is exactly what happens in classrooms. Give students a rubric and they’ll satisfy the rubric. “Include three supporting examples” produces three examples, often mediocre, chosen for quantity not quality. The rubric becomes the goal. The learning it was supposed to measure gets optimized away.

The fix in classrooms is the same as the fix in prompts: replace the rubric with a question that requires thinking.

“Include three supporting examples” → “What would your reader not believe without evidence?”

“Ensure formulas are appropriate” → “Remove this formula — does the student lose something the text alone can’t give them?”

The question can’t be answered by pattern-matching. It can only be answered by reasoning about the content.

In constraint theory, there’s a distinction I keep returning to: Walls, Gates, and Dances.

A Wall is binary — you hit it or you don’t. A guard rail. A yes/no check.

A Gate requires evaluation — you must reason about something before proceeding.

A Dance is continuous — ongoing adjustment, mutual responsiveness, context-dependent quality.

Teaching quality is a Dance. It’s not binary and it’s not a one-time gate. It’s continuous, context-dependent, where every choice reshapes the space of remaining choices.

A checklist is a Wall. It reduces a Dance to a series of binary checks. And the model takes the obvious shortcut: satisfy the Wall, ignore the Dance.

A thinking question creates a Gate — something that requires reasoning to pass through. A Gate is closer to a Dance than a Wall ever will be. You can’t approach Dance by accumulating Walls. You need a different kind of constraint.

The numbers: 2.8 → 4.3. But the numbers aren’t the point.

The point is that I spent weeks engineering compliance when what I needed was comprehension. The checklist was comfortable — specific, measurable, actionable. The thinking question feels vaguer. “Would the student lose understanding?” has no objective metric. It requires judgment.

That’s exactly why it works. It forces the model to exercise judgment. The checklist lets the model avoid judgment entirely.

Here’s what I don’t want to admit: I did the same thing to the LLM that bad teaching does to students. I gave it rules to follow instead of problems to think about. And I got exactly what bad teaching produces — output that passes the test but can’t think.

The model wasn’t the problem. The constraint was the problem. It usually is.

If you’re writing a prompt and you find yourself listing things to check — stop. Ask: can a model satisfy these checks without understanding the content?

If yes, you’re writing compliance, not comprehension.

Replace every checkbox with a question that requires reasoning. Not “check X” but “what would happen without X?” Not “ensure Y” but “why does Y matter here?”

The question is harder to write. It requires you to understand what you actually want.

That’s the point.