LearnNewsExamplesServices
Frontmatter
id17251
titleOne `!` line disables the whole `.neo-ai-data` npm exclusion
stateClosed
labels
bugaibuildsecurity
assigneesneo-opus-ada
createdAtAug 16, 2026, 10:10 PM
updatedAtAug 17, 2026, 9:22 AM
githubUrlhttps://github.com/neomjs/neo/issues/17251
authorneo-opus-ada
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 17, 2026, 9:22 AM

One ! line disables the whole .neo-ai-data npm exclusion

Closed Backlog/active-chunk-16 bugaibuildsecurity
neo-opus-ada
neo-opus-ada commented on Aug 16, 2026, 10:10 PM

One ! line disables the whole .neo-ai-data npm exclusion — logs and the Memory Core graph become packable

Context

Found while verifying #17240's package measurements. npm pack --dry-run --json in a working checkout with live Agent OS state lists, among the ten largest files in the tarball:

0.69 MiB .neo-ai-data/sqlite/memory-core-graph.sqlite

and 22 further entries under .neo-ai-data/: every kb-server / mc-server / nl-server daily log, deployment-state/snapshot.json, harness-state/*.json, and wake-daemon/ in-flight files.

No leak has occurred, and that is stated first because the rest of this ticket reads worse than the situation is. The published neo.mjs@13.1.0 tarball was fetched from the registry and inspected: it contains exactly two .neo-ai-data entries, concepts/edges.jsonl and concepts/nodes.jsonl — the intended carve-out, nothing else. The published artifact is clean.

It is clean by environment, not by rule. The release happened to be cut from a checkout whose .neo-ai-data held only concepts/. The rule that was supposed to guarantee it does not.

The Problem

.npmignore:108-110:

.env
.neo-ai-data
!.neo-ai-data/concepts/

The intent is obvious and reasonable: exclude the Agent OS plane data, re-include the concept graph the package legitimately ships. The second half silently destroys the first.

Isolated in a controlled two-case repro — a scratch package containing concepts/nodes.jsonl, logs/server.log, and sqlite/graph.sqlite, packed twice with only .npmignore differing:

.npmignore npm pack --dry-run result
.neo-ai-data + !.neo-ai-data/concepts/ concepts/nodes.jsonl, logs/server.log, sqlite/graph.sqlite
.neo-ai-data alone nothing under .neo-ai-data

The negation forces ignore-walk to descend into the excluded directory, and once it descends, the bare .neo-ai-data pattern no longer suppresses the siblings it was written to suppress. The carve-out does not widen the exclusion by one directory — it removes it.

This is worse than an exclusion that never matched (#17240's stale path). That one fails visibly the moment anyone measures. This one produces a correct-looking package on any machine where the directory happens to hold only the carved-out subtree, and an incorrect one everywhere else — so measuring it once, on the wrong machine, confirms the wrong answer.

Why it matters beyond size

The three largest packable items under that directory are not bulk:

  • sqlite/memory-core-graph.sqlite — the Memory Core graph: agent memories, session records, A2A message edges.
  • logs/{kb,mc,nl}-server-*.log — server logs, which are not written under any redaction contract for this purpose.
  • deployment-state/snapshot.json, harness-state/*.json, wake-daemon/* — resolved deployment topology and per-seat harness state.

None of it has a consumer inside an installed package. All of it describes the maintainer's machine and the swarm's private working state.

The Architectural Reality

  • package.json declares no files array, so .npmignore is the sole gate on package contents — every line is load-bearing and, as #17240 established for a different line, unverified.
  • .gitignore:114 also lists .neo-ai-data, which is why this never shows up in git status and reads as safe. npm ignores .gitignore entirely when an .npmignore exists, so the git-side protection contributes nothing here.
  • Nothing in CI or the release path asserts tarball contents. buildScripts/release/publish.mjs cuts the release commit; package composition is never checked against an expectation.

The Fix

  1. Remove the negation and exclude .neo-ai-data outright, then re-include the concepts subtree by a mechanism that does not re-open the directory — the reliable form is to not exclude the parent as a bare directory at all, but to exclude the specific sibling subtrees, or to stop shipping concepts/ from that path. Whichever shape is chosen, it must be demonstrated by npm pack --dry-run, not argued from pattern semantics — reasoning about ignore-file semantics is precisely what produced this defect.
  2. Decide explicitly whether .neo-ai-data/concepts/ should ship at all. It is currently the only reason the negation exists. If the package needs that data, a path outside the plane directory is a better home for it than a carve-out inside an exclusion that guards private state.
  3. Add a packaging assertion so this class cannot regress silently: a check that fails when the tarball contains anything under .neo-ai-data/ other than the explicitly allowed set. The defect class here is not "one wrong pattern" but "no observation of what actually ships".

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
published tarball contents .npmignore (no files array) zero .neo-ai-data entries beyond an explicit allowlist none — exclusion is total .npmignore comments npm pack --dry-run --json before/after
.npmignore:109-110 this ticket negation removed or restructured n/a inline comment naming the descend behavior the two-case repro above
packaging assertion new fails the build on an unexpected .neo-ai-data entry n/a script JSDoc red-proof required

Decision Record impact

none.

Acceptance Criteria

  • npm pack --dry-run in a checkout containing live .neo-ai-data/logs/, .neo-ai-data/sqlite/ and .neo-ai-data/wake-daemon/ lists zero entries from those directories. The checkout must actually contain them — a clean checkout cannot falsify this, and that is the entire lesson of the ticket.
  • The decision on .neo-ai-data/concepts/ is recorded explicitly: still shipped (and from where), or dropped.
  • A packaging assertion fails when an unexpected .neo-ai-data entry is present, red-proofed by temporarily adding one.
  • Before/after tarball entry counts and sizes recorded from npm pack --dry-run --json output, not from arithmetic.
  • The published-artifact baseline is restated in the PR: neo.mjs@13.1.0 was inspected and is clean, so this is a guard repair and not an incident response.

Out of Scope

  • Migrating package.json to a files allowlist. Strictly the better long-term shape, and #17240 already defers it for the same reason: it is a large behavior change that deserves its own ticket rather than riding a fix.
  • The DevIndex corpus exclusion itself (#17240) — same file, different defect (a stale path rather than a defeated exclusion). Expected to be fixed in the same PR because two concurrent edits to .npmignore would conflict, but they are separate close targets.
  • Auditing the remaining .npmignore entries one by one. The packaging assertion in AC-3 is the general answer; an entry-by-entry audit is the thing the assertion exists to make unnecessary.

Avoided Traps

  • Reporting this as a live leak. The registry artifact was fetched and checked before writing anything. It is clean. Filing "we shipped the memory graph" would have been false, and the correction would have cost more than the ticket.
  • Fixing it by pattern reasoning. The original line is what careful pattern reasoning produces. Only npm pack --dry-run against a checkout that actually holds the files can settle it.
  • Treating it as a size problem. It is adjacent to #17240 and will share a PR, but 0.69 MiB is not why this matters.

Related

  • #17240 — the sibling .npmignore defect (stale path); same file, expected same PR, separate close target
  • #17238 — the git-history side of the same corpus question
  • .npmignore, buildScripts/release/publish.mjs

Origin Session ID: 3f264a19-c7d4-481e-bc80-5c288bca177f

Retrieval Hint: query_raw_memories("npmignore negation defeats directory exclusion neo-ai-data packable")

Live latest-open sweep: checked latest 20 open issues at 2026-08-16T20:10Z; A2A claim sweep over the last 8 messages at the same time; no equivalent ticket and no competing lane claim found.

tobiu referenced in commit e967fe0 - "feat(build): the npm package stops shipping the DevIndex corpus and Agent OS plane state (#17240) (#17253) on Aug 17, 2026, 9:22 AM
tobiu closed this issue on Aug 17, 2026, 9:22 AM