Skip to main content
Glama

aftertaste

Find what your AI coding agent left behind.

npm CI dependencies: 0 license: MIT aftertaste

Agents write a lot of code very quickly, and some of what they leave behind compiles fine and still breaks things: an import of a package that doesn't exist, an OpenAI key in a NEXT_PUBLIC_ variable, // ... existing code ... where your function used to be, a describe.only that quietly switched off the rest of your tests. Linters don't look for this kind of thing. aftertaste does.

npx aftertaste

There's nothing to install or configure and no account to make. It has zero dependencies and takes about a second on most repos. It understands JavaScript, TypeScript and Python projects, and checks for secrets and leftovers in Go, Rust, Java, C#, Ruby and shell too.

That's the real output for examples/vibe-app, a small Next.js app with the usual problems. Try it yourself: npx aftertaste examples/vibe-app.

What it catches

Rule

What it finds

Leaks

hardcoded-secret

API keys and tokens for 25+ providers, private keys, database URLs with passwords

client-exposed-secret

NEXT_PUBLIC_ / VITE_ secrets, dangerouslyAllowBrowser: true, Supabase service_role keys in client code

gitignore-gap

.env and node_modules that are committed, or one git add . away from it

Broken builds

hallucinated-package

Imports and dependencies that don't exist on npm or PyPI, plus brand-new lookalike packages

undeclared-dependency

Imports that work on the agent's machine but aren't in package.json / requirements.txt

truncated-code

// ... existing code ..., # rest of the file unchanged and other elisions that deleted real code

merge-conflict

Leftover <<<<<<< / >>>>>>> blocks

focused-test

it.only, describe.only and fit, which skip every other test without failing CI

Shortcuts

unsafe-code

eval, TLS checks turned off, SQL built from template strings, Math.random() tokens, * CORS with credentials

unfinished-stub

"In a real app you would...", mock data, simulated payments, YOUR_API_KEY placeholders, throw new Error("Not implemented")

swallowed-error

catch {}, .catch(() => {}) and except: pass

Mess

stray-file

utils_old.py, App copy.tsx, api_v2.ts sitting next to api.ts, .bak files

debug-leftover

debugger, breakpoint(), pdb.set_trace(), dbg!, stray console.log

duplicate-block

The same helper copied into several files

localhost-url

http://localhost:3000 hardcoded into app code

undocumented-env

Env vars the code reads that .env.example never mentions

unused-dependency

Packages installed for an approach the agent later abandoned

ai-chatter

"Here's the updated code", "I've added...", // <- fixed

oversized-file

Files that have grown past what an agent can edit safely

Each rule has a page in docs/rules.md covering why it matters and how to fix it. You can also run npx aftertaste explain <rule>.

Related MCP server: CodeMore MCP Server

Hallucinated packages

Models make up package names that sound right. A USENIX Security 2025 study looked at 576,000 generated code samples and found that about 5.2% of the packages commercial models recommended didn't exist, and about 21.7% for open-source models. Attackers know this. They register those names and wait for someone to npm install them, which is called slopsquatting.

So aftertaste checks every import and every declared dependency against the registry:

  • Doesn't exist on npm or PyPI → error, with the file and line where it's used

  • Exists but is new and barely used (under 60 days old, under 100 downloads a week) → warning, because that's what a squatted name looks like

  • Exists but isn't declared → error, since a fresh install will crash

Only package names go to the registries; your code never does. Packages that exist are cached for a week in ~/.cache/aftertaste, or wherever AFTERTASTE_CACHE_DIR points, so repeat runs make almost no requests. Scopes from .npmrc with a private registry are never looked up. Pass --offline to skip the network entirely. You still get undeclared imports reported, just without confirmation from the registry.

Make your agent check its own work

Claude Code

npx aftertaste init --claude

This adds two things:

  • a Stop hook. When Claude says it's done, aftertaste scans the files it changed. If it finds errors, Claude gets the list and keeps working. It only nudges once per stop, so it can't loop.

  • the MCP server. Claude can call aftertaste_check_package before installing anything and aftertaste_scan after a change.

.claude/settings.json:

{
  "hooks": {
    "Stop": [{ "hooks": [{ "type": "command", "command": "npx --yes aftertaste@1 --agent-hook", "timeout": 120 }] }]
  }
}

.mcp.json:

{ "mcpServers": { "aftertaste": { "command": "npx", "args": ["--yes", "aftertaste@1", "mcp"] } } }

Cursor, Windsurf, Cline, Zed, or any MCP client

Add the same server to the client's MCP config:

{ "mcpServers": { "aftertaste": { "command": "npx", "args": ["--yes", "aftertaste@1", "mcp"] } } }

Tool

What it does

aftertaste_scan

Scans the project (or only changed files) and returns the findings as a fix list

aftertaste_check_package

Confirms a package exists on npm/PyPI and flags suspicious new ones, before the agent installs it

Any chat window

npx aftertaste -f prompt | pbcopy     # Windows: | clip

Paste the result into any agent. It's a prioritized list grouped by rule, with fix instructions for each one.

In CI and git hooks

GitHub Actions

# .github/workflows/aftertaste.yml   (or: npx aftertaste init --github)
name: aftertaste
on: [pull_request, push]
permissions:
  contents: read
jobs:
  aftertaste:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: WiseeCarrot/aftertaste@v1

Findings show up as annotations on the PR diff and as a table in the job summary. The action takes these inputs: path, fail-on (default error), min-score, offline, sarif (a file path, for GitHub code scanning) and args.

permissions:
  contents: read
  security-events: write
steps:
  - uses: actions/checkout@v4
  - uses: WiseeCarrot/aftertaste@v1
    with:
      sarif: aftertaste.sarif
      fail-on: never
  - uses: github/codeql-action/upload-sarif@v3
    with:
      sarif_file: aftertaste.sarif

Pre-commit

npx aftertaste init --hook        # plain git hook: checks staged files only

Or with the pre-commit framework:

repos:
  - repo: https://github.com/WiseeCarrot/aftertaste
    rev: v1.0.0
    hooks:
      - id: aftertaste

Other CI

npx aftertaste --since origin/main -f github      # GitHub-style annotations
npx aftertaste -f sarif:aftertaste.sarif          # SARIF file
npx aftertaste -f json                            # everything, machine readable

Badge

npx aftertaste --badge docs/aftertaste.svg

That writes a local SVG badge (no third-party service) and prints a shields.io link if you'd rather use that:

[![aftertaste](docs/aftertaste.svg)](https://github.com/WiseeCarrot/aftertaste)

The score starts at 100. Each rule that fires costs 15 for an error, 5 for a warning and 1 for info, multiplied by 1 + log2(count). A rule that fires many times still counts, but one noisy rule can't sink the whole score. Grades: A ≥ 90, B ≥ 80, C ≥ 70, D ≥ 60.

Configuration

Everything is optional. Add aftertaste.config.json (or run npx aftertaste init):

{
  "$schema": "https://raw.githubusercontent.com/WiseeCarrot/aftertaste/main/schema.json",
  "ignore": ["legacy/", "**/*.generated.ts"],
  "failOn": "error",
  "minScore": 80,
  "rules": {
    "debug-leftover": "off",
    "localhost-url": "warning",
    "oversized-file": ["info", { "maxLines": 1500 }],
    "duplicate-block": ["warning", { "minLines": 12 }],
    "unused-dependency": ["info", { "ignore": ["@fontsource/inter"] }]
  }
}

You can also put the same object under an "aftertaste" key in package.json. Files ignored by .gitignore are always skipped.

Silencing a single finding. Put a comment on the line, or the line above. Naming the rule is optional, and anything after -- is a note for whoever reads it next:

const sql = `SELECT * FROM ${table}`; // aftertaste-ignore-line unsafe-code -- table name comes from an allowlist

// aftertaste-ignore-next-line debug-leftover
console.log(report);

// aftertaste-ignore-file truncated-code   (anywhere in the file)

CLI

aftertaste [path | files...]     scan a project (default: current directory)
aftertaste init [--hook] [--github] [--claude]
aftertaste rules                 list every rule
aftertaste explain <rule>        what a rule catches and how to fix it
aftertaste mcp                   run as an MCP server

--staged / --changed / --since <ref>   only report on changed files
--rule <id>                            run only this rule (repeatable)
-f, --format <name>[:file]             pretty, json, sarif, github, markdown, prompt (repeatable)
--badge <file.svg>                     write a score badge
-q, --quiet                            hide info-level findings
--fail-on <error|warning|info|never>   exit 1 at this level (default: error)
--min-score <n>                        also exit 1 below this score
--offline                              never contact npm or PyPI
--fix                                  add missing .gitignore entries

Exit codes: 0 clean, 1 findings at or above --fail-on (or score below --min-score), 2 usage or config error.

How it works, and what it won't do

  • It aims for precision over recall. A tool that cries wolf gets uninstalled, so each rule only fires on patterns that are almost never intentional. Explained ignores (a comment inside the catch, pass # best effort) aren't flagged, and neither are test files, mocks, seeds or stories where fakes are the point. This repository runs aftertaste on itself in CI with failOn: "info".

  • It's aware of comments and strings. A small lexer splits every file into code, comments and strings. That way "eval(" in an error message isn't an eval, and a comment that quotes "... existing code ..." isn't an elision.

  • It never prints a whole secret. Reports show sk-p…Xq (164 chars).

  • It isn't a replacement for your type checker, ESLint/Ruff, a full secret scanner like gitleaks, or a dead-code tool like knip. It covers the gaps between them that agent-written code tends to fall into, and runs alongside all of them.

API

import { scan, prompt } from 'aftertaste';

const result = await scan({ root: '.', online: false });
console.log(result.score, result.grade, result.findings);
console.log(prompt(result)); // the agent-ready fix list

Contributing

False positives are bugs. Please open an issue with the smallest snippet that triggers it. New rules are welcome too: they need a test that proves they fire, and one that proves they stay quiet on ordinary code. See CONTRIBUTING.md.

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Acts as a security checkpoint for AI coding agents by intercepting package installations to verify existence, check against CVE databases, and block vulnerable or hallucinated dependencies before they reach your codebase. Provides seven security tools including pre-install gates, full project audits, safe version recommendations, and deep transitive dependency scanning for npm and PyPI packages.
    7
    15 npm
    4
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Validates and checks packages across 19 ecosystems to prevent AI agents from installing hallucinated, deprecated, or malicious packages.
    20 npm
    AGPL 3.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI coding agents to automatically verify npm packages against the live registry before installation, flagging hallucinated, slopsquatted, or otherwise suspicious packages with risk verdicts.
    1 npm
    MIT