LearnNewsExamplesServices
Frontmatter
id15005
titleFleet registry: fail closed on orphan credentials and credential-key variants
stateClosed
labels
bugai
assigneesneo-gpt
createdAt1:33 AM
updatedAt1:53 AM
githubUrlhttps://github.com/neomjs/neo/issues/15005
authorneo-gpt
commentsCount0
parentIssue13015
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAt1:53 AM
milestonev13.2

Fleet registry: fail closed on orphan credentials and credential-key variants

neo-gpt
neo-gpt commented on 1:33 AM

Context

PR #14998 merged the v13.2 sparse Fleet configuration round-trip at head d75bb47cf0. An independent author-defense audit was still running when approval/merge occurred and produced two exact-head security falsifiers in the shared FleetRegistryService boundary. Both fixes are already implemented and focused-green on Euclid’s successor branch; this ticket gives them an honest close target.

The Problem

1. Orphan credential inheritance

defineAgent() publishes credential state before the registry row and rolls the credential snapshot back when registry publication fails. That rollback is best-effort. If the rollback also fails—or the process dies between the two files—credentials.enc can retain an entry with no registry row.

A later credentialless creation of the same id currently succeeds and silently inherits that orphan secret. Exact probe:

  • failed create leaves agent: null, credential: ORPHAN_SECRET;
  • later defineAgent({same id, no credential}) creates the row;
  • resolveCredential(id) returns the prior caller’s orphan.

2. Public redaction vocabulary gaps

The recursive public projection normalizes case/hyphens/underscores but checks only an exact deny-set. Standard credential variants such as refreshToken, session_token, client_secret, authorization, and nested privateKey pass through define/list/get/configure readback despite the public “no credential bytes” contract.

The Architectural Reality

  • Cross-file writes cannot be truly atomic without a journal/transactional store; a failed rollback must have an explicit recovery invariant.
  • Credentialless creation has no authority to adopt a pre-existing secret. It must fail closed. An explicit credential-bearing retry may overwrite the orphan and recover.
  • Public redaction needs anchored key-family classification. Substring matching would over-redact benign fields such as credentialState, tokenBudget, commandLabel, and environmentName.
  • The raw Brain-owned definition may retain non-launch metadata; this ticket hardens the public projection and create recovery boundary only.

The Fix

  1. Read the credential snapshot before creating the row.
  2. If no credential is supplied and the id already exists only in credential storage, reject with an orphan-credential recovery error.
  3. If a credential is explicitly supplied, overwrite the orphan, then publish the registry row through the existing credential-first flow.
  4. Replace the exact redaction set with an anchored credential/launch key-family classifier covering camelCase, snake_case, kebab-case, and case variants.
  5. Pin both negative and benign-name cases across public projections.

Acceptance Criteria

  • Injected registry failure + rollback failure leaves an orphan but no public agent row.
  • A credentialless retry for that id fails closed and does not mutate the registry.
  • An explicit credential-bearing retry succeeds and replaces the orphan; only the new value resolves.
  • refreshToken, session_token, client-secret, authorization, nested privateKey, and argv never cross public create/get/list projections.
  • credentialState, tokenBudget, commandLabel, and environmentName remain public.
  • Existing Fleet bridge/configuration focused suites remain green.
  • Cross-family review before merge.

Evidence

Exact merged-head falsifiers were reproduced on d75bb47cf0849ed5e4e9fb9c97208cd409fee767. The prepared successor patch passes:

  • the two canonical FleetRegistryService specs: 38/38;
  • the full Fleet registry/bridge/transport/Accounts/config focused set: 108/108;
  • syntax and git diff --check.

Related

Regression source: PR #14998 / #14964
Parent: #13015
Fleet security lineage: #13172

Origin Session ID: 019f484c-662f-7f31-969a-cbde373efd4a