Skip to content

gstack-learnings-search cross-project trust gate fails open for rows missing the trusted field #1745

Closed

Description

@jbetala7

severity: security (defense-in-depth)

Observed problem

bin/gstack-learnings-search --cross-project is meant to be an allowlist: per the inline comment, cross-project learnings are "only loaded if trusted (user-stated)" to prevent prompt injection from one project's AI-generated learnings silently influencing reviews in another project.

The gate is implemented as a denylist instead:

if (isCrossProject && e.trusted === false) continue;

Any cross-project row where trusted is missing/undefined (not literally false) is admitted, because undefined === false is false. So legacy rows written before the trusted field existed, hand-edited rows, and rows produced by other tools all bypass the gate.

The trusted field was added in the security wave 3 (#988, 2026-04-13), while gstack-learnings-search shipped earlier (#622, 2026-03-29). Any learnings.jsonl written in that window — or by anything that does not set the field — has rows with no trusted key. Those rows leak across projects today.

Current behavior on upstream main (a6fb317)

A foreign project row with no trusted field and source: observed is loaded and shown as [cross-project]:

$ # foreign learnings.jsonl row, no trusted field:
$ # {"ts":"2026-05-09T00:00:00Z","type":"pattern","key":"foreign-legacy","insight":"INJECTED legacy guidance with no trusted field","source":"observed"}
$ gstack-learnings-search --cross-project --query INJECTED
LEARNINGS: 1 loaded (1 pattern)

## Patterns
- [foreign-legacy] (confidence: 5/10, observed, 2026-05-09) [cross-project]
  INJECTED legacy guidance with no trusted field

Expected behavior

The gate should fail closed: a cross-project row is admitted only when trusted === true. Rows missing the field are treated as untrusted. Current-format rows are unaffected (user-stated rows carry trusted: true and still load; everything else carries trusted: false and is still excluded).

Duplicate searches performed

Candidate fix shape

Change the cross-project trust check from a denylist (e.trusted === false) to an allowlist (e.trusted !== true), and add a regression test for a foreign row with no trusted field.

Activity

  1. garrytan commented on Jun 8, 2026

    @garrytan
    Owner

    Fixed in #1911 (v1.57.6.0). The cross-project trust gate is now a true allowlist (trusted !== true), so rows missing the field are excluded. A follow-up commit also removed backticks from the surrounding comment (they were command-substituted inside the bun -e block). Thanks @jbetala7. Auto-closes on merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions