Skip to content

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

@jbetala7

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.

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