Skip to content

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).

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 .map file, or an inline map at the end of a .js, .mjs or .cjs file): 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.

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.

  • No licence found? Check that first. If the package is open source, add its licence: in npm, a license field such as "license": "MIT" in package.json; in Python, license = "MIT" in pyproject.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": false or "inlineSources": false; esbuild --sources-content=false; Vite build.sourcemap: false; webpack devtool: false; Rollup output.sourcemap: false.
  • Or keep the files out of the package with .npmignore or the files list in package.json (for Python, your build tool’s exclude list). Keep src/ and AI instruction files out too.
  • Open source, and you did not mean to ship the full source inside the maps? Turn off sourcesContent and keep the maps: tsc "inlineSources": false; esbuild --sources-content=false; Rollup output.sourcemapExcludeSources: true (in Vite, under build.rollupOptions); webpack devtool: "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.

  • Files are read within the pack limits: 10 MB each and 64 MB per package. A bundle over the limit is not checked for an inline map. A .map file 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 ..., proprietary or commercial; PyPI a License of Proprietary, Commercial or All rights reserved, or the Proprietary classifier; or a licence expression with no open choice, where every OR choice holds a LicenseRef- id. So MIT AND LicenseRef-X is closed, and AGPL-3.0-only OR LicenseRef-Commercial is open. If it calls an open package closed, use a standard SPDX licence id, or add one as an OR choice.
  • 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, .npmrc or a scoped @scope:registry setting isn’t in the package, so those prints keep their severity.
  • It runs in pack and the publish guard only, not in scan or the git push check.