AP009: Package lets an AI rebuild your source
| Severity | high to low by licence; no licence, UNLICENSED, SEE LICENSE IN, proprietary or only LicenseRef- choices count as closed; low for a private registry |
| Runs in | pack, guard hook |
| One-line fix | Turn off source maps or their sourcesContent in the build, and keep original source and AI instruction files out of the package (.npmignore or the package.json “files” list; your Python build tool’s exclude list). |
What it finds
Section titled “What it finds”Four kinds of thing in a package you are about to publish, with one print per kind and an example path:
- Source maps that hold your original source (a
.mapfile, or an inline map at the end of a.js,.mjsor.cjsfile): the full code, comments included. - Source maps without the source: your file layout and original names, enough to de-minify.
- Original source next to the compiled output (
.ts,.tsx,.mts,.cts,.vue,.svelte; never.d.ts). - AI instruction files (
CLAUDE.md,AGENTS.md,GEMINI.md,.cursorrules,.windsurfrules,.github/copilot-instructions.md,.cursor/rules/*): a written tour of your codebase.
Severity depends on your licence. For a closed-source package, maps with source are high and the
other kinds are medium. For an open-source package, only maps with source are mentioned, as a low
note: the full original source ships inside them, which is normal for open source, so nothing is
wrong. If publishConfig.registry in package.json is anything but the public npm registry, the
package goes to a private registry: every print is a low note, and it says “published to a private
registry”. The print never shows file contents.
Why it matters
Section titled “Why it matters”Nobody can stop people reverse engineering code you ship, and AI makes it faster. This check does not claim to prevent it. It shows what in your package hands an AI your original source, or a map of it, so the giveaways that were not on purpose can come out before you publish.
How to fix
Section titled “How to fix”- No licence found? Check that first. If the package is open source, add its licence: in npm,
a license field such as
"license": "MIT"inpackage.json; in Python,license = "MIT"inpyproject.toml. That is the whole fix, and the medium and high prints go away. Your AI agent is told to ask you before it changes the build. - For a closed-source package, turn off the maps, or the source inside them, in your build: tsc
"sourceMap": falseor"inlineSources": false; esbuild--sources-content=false; Vitebuild.sourcemap: false; webpackdevtool: false; Rollupoutput.sourcemap: false. - Or keep the files out of the package with
.npmignoreor thefileslist inpackage.json(for Python, your build tool’s exclude list). Keepsrc/and AI instruction files out too. - Open source, and you did not mean to ship the full source inside the maps? Turn off
sourcesContentand keep the maps: tsc"inlineSources": false; esbuild--sources-content=false; Rollupoutput.sourcemapExcludeSources: true(in Vite, underbuild.rollupOptions); webpackdevtool: "nosources-source-map". This is optional.
agentprint fix lists a closed-source package’s prints under “Needs you”, and the low notes (open
source, or a private registry) under “Notes”. It never edits your build settings.
Limits
Section titled “Limits”- Files are read within the
packlimits: 10 MB each and 64 MB per package. A bundle over the limit is not checked for an inline map. A.mapfile over the limit still counts by its name. The first print says how many files were not read. - Bundled dependencies in
node_modules/inside the package are skipped: they are not your source. - “Original source” is only flagged when compiled JavaScript ships too. A package that is only source is the product itself.
- Minified or obfuscated code without maps can still be reverse engineered by an AI. This check does not say otherwise.
- The licence rule decides closed or open. Closed means: no licence found, or unreadable; npm
UNLICENSED,SEE LICENSE IN ...,proprietaryorcommercial; PyPI a License of Proprietary, Commercial or All rights reserved, or the Proprietary classifier; or a licence expression with no open choice, where everyORchoice holds aLicenseRef-id. SoMIT AND LicenseRef-Xis closed, andAGPL-3.0-only OR LicenseRef-Commercialis open. If it calls an open package closed, use a standard SPDX licence id, or add one as anORchoice. - Python package metadata is read for its licence from its first 1 MB.
- A private registry is seen only in
publishConfig.registry. One chosen with--registry,.npmrcor a scoped@scope:registrysetting isn’t in the package, so those prints keep their severity. - It runs in
packand the publish guard only, not inscanor the git push check.