Skip to content

[plugins] discovery (PR #2): cache-key token identity, bounded cache + review follow-ups #4

Description

@kreneskyp

Context

feat/plugin-discovery (PR #2) adds src/search.ts (the plugin-discovery API: searchPlugins / createPluginSearch / createTtlCache / sourceToInstallInput). It is green (tsc, 100% coverage, eslint, quire) and was smoke-tested live (found 8 @agent-ix/spec-* packages on npm). A /code-review + /gap-analysis pass (SpecReview SR-005, reviews/26-06-27-plugin-discovery-gap-analysis.md) found the items below. None are merge-blockers, but the first two are real (privacy + memory) and one is a spec↔code divergence.

Tasks

  • FND-002 — Medium (privacy) + spec divergence. createPluginSearch cacheKey (src/search.ts ~line 568) keys on token presence (token ? "auth" : "anon"). FR-010 §Behavior (spec/functional/FR-010-discovery-cache.md ~line 44) specifies "a stable non-secret discriminator of the resolved token". Two distinct tokens currently collide on "auth", so with the late-bound-token feature (FR-010-AC-5 / TC-036) a shared searcher could serve user A's authenticated (incl. private-repo) GitHub results to user B. Fix: fold a stable non-secret hash of the resolved token (e.g. first 8 hex of SHA-256, computed via node:crypto) into the key instead of the boolean. Add a test: two different non-empty tokens produce distinct cache entries (no cross-token hit).
  • FND-003 — Medium (memory). createPluginSearch leaves cacheMax undefined by default (src/search.ts ~line 546), so createTtlCache sets no max and entries evict only lazily on read of an expired key — memory grows with distinct tag|query|sources|limit searches over the TTL. Fix: default cacheMax (e.g. 256) in createPluginSearch. Add a test: LRU eviction past the bound.
  • FND-004 — Low (in-CDN path traversal). manifestUrl (src/search.ts ~line 421) interpolates the registry-supplied name/fullName unencoded into https://unpkg.com/{name}/{manifestPath} and https://raw.githubusercontent.com/{fullName}/HEAD/{manifestPath}. Cross-host SSRF is not reachable (fixed authority), but a name containing .. can traverse within the trusted CDN. Fix: reject/encode path segments containing .. or control chars in the registry-derived name before building the URL. (manifestPath is host-supplied → trusted.)
  • FND-005 — Low. parseRate Number(header) (src/search.ts ~line 331) has no non-finite guard → surfaces NaN rate fields; treat a non-finite parse as "no rate info" (return undefined). A cache hit returns the shared SearchResponse reference (~line 576) → return a shallow clone or document immutability.
  • FND-001 — process. Flip plan/Plan-001-plugin-discovery/tasks/Task-001..007 status: todo → done (they are implemented + verified). Task-008 (publish) stays pending.

Acceptance criteria

  • Distinct non-empty tokens → distinct cache entries (test); no token value ever appears in a key (CON-2 still holds).
  • Cache bounded by default + LRU-eviction test.
  • Manifest name with .. is rejected/encoded (test).
  • src/search.ts stays at 100% coverage; quire validate --scope . "spec/**/*.md" clean.
  • Gap-analysis verdict clears toward PASS.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    pluginsPlugin system / discovery / marketplace

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions