Commit Graph

2 Commits

Author SHA1 Message Date
Anso c677b8bb66 fix: fall back to enroll token when pilot tunnel JWT is rejected (#1566)
* fix: fall back to enroll token when pilot tunnel JWT is rejected

On HTTP 401/404 upgrade rejection, delete stale pilot.jwt and retry with SENCHO_ENROLL_TOKEN.

* test: import pilot agent module after DATA_DIR is set in fallback test

The auth-fallback test statically imported pilot/agent, which freezes its
pilot.jwt path from DATA_DIR at module load, before setupTestDb redirects
DATA_DIR to a writable temp dir. On the Linux CI runner the path resolved
to a non-writable /app/data, so persistToken silently failed and the
round-trip assertion read null. Import the module dynamically in beforeAll
after setupTestDb, matching the sibling unit test.

* fix: remove unexpected-response listener that blocked pilot reconnect

The ws library skips abortHandshake when an unexpected-response listener
exists, so error and close never fire and the agent hangs in CONNECTING.
Detect auth rejection via the abortHandshake error message instead; the
close handler already performs enroll-token fallback and reconnect.
2026-07-05 01:03:09 -04:00
Anso 8f13a7faf3 fix(pilot): stop silently swallowing fs errors in agent token helpers (#985)
* fix(pilot): stop silently swallowing fs errors in agent token helpers

The pilot-agent audit found that both filesystem-touching helpers in
the agent process discarded every fs error class:

  - readPersistedToken caught all errors and returned null. ENOENT
    (normal first boot) and EACCES / EIO / EROFS (a real disk or
    volume-permission failure) were indistinguishable; the latter
    silently fell back to "no persisted token", and on a node where
    the enrollment token had already been consumed the agent would
    enter a re-enrollment loop with no log signal pointing at the disk.
  - persistToken caught all errors and logged at console.warn. The
    operator saw the next-boot loop with the same diagnostic gap.

Replace both with errno-aware handling:

  - readPersistedToken calls fs.readFileSync directly (no TOCTOU race
    against existsSync), treats ENOENT as silent, and logs every other
    errno at ERROR with the path.
  - persistToken logs at ERROR (not WARN) with the failing errno and an
    explicit "next agent restart will require re-enrollment until the
    volume is writable" message. The agent still continues with the
    in-memory token so the current session is unaffected.

Both helpers are now exported (marked @internal) so the new test file
can mock fs and assert the error-class-vs-log-level matrix.

12 unit cases in pilot-agent-fs-errors.test.ts cover ENOENT,
EACCES, EIO, EROFS, ENOSPC, missing errno, empty file, and the
happy path. Mock pattern follows backend/src/__tests__/filesystem.test.ts.

* fix(pilot): address code-review on the fs-error branch

Three findings from the review pass:

  - Em dash in a test description (Directive 18). Rewritten as
    "does not throw, so the in-memory token stays usable for the
    current session".
  - persistToken still had an existsSync + mkdirSync pair around
    the token-write. mkdirSync({ recursive: true }) is idempotent on
    existing directories, so the existsSync was redundant and added
    a TOCTOU window where the directory could be removed between the
    probe and the write. Dropped the existsSync; the test that
    previously primed mockExistsSync now asserts mockExistsSync is
    NOT called, locking the TOCTOU removal.
  - The two new exports used the @internal JSDoc tag, but this
    repo's existing pattern for "public-by-convention-for-tests"
    helpers (e.g. RegistryService.ts:481) is plain prose
    "Exposed for unit tests." Switched to that style.

No behavior change beyond the TOCTOU removal.
2026-05-08 03:48:35 -04:00