Skip to content

Improvement to flub check trustPolicy: report why a particular package version is flagged as a trust downgrade#27254

Open
TommyBrosman wants to merge 6 commits intomicrosoft:mainfrom
TommyBrosman:tbrosman/trust-policy-output-fix
Open

Improvement to flub check trustPolicy: report why a particular package version is flagged as a trust downgrade#27254
TommyBrosman wants to merge 6 commits intomicrosoft:mainfrom
TommyBrosman:tbrosman/trust-policy-output-fix

Conversation

@TommyBrosman
Copy link
Copy Markdown
Contributor

Extends the flub check trustPolicy command so that each ERR_PNPM_TRUST_POLICY_VIOLATION reported by pnpm is enriched with publish metadata fetched directly from the registry, making it possible to triage violations without manually digging through npm view output.

Description

For every violation the report now shows:

  • Offender: publish timestamp, publishing account, and trust evidence kind (none / provenance / trustedPublisher) — matching pnpm's own prettyPrintTrustEvidence formatting.
  • Prior trusted: the most recent earlier-published version of the same package that carried trust evidence (the version pnpm's policy is comparing against).

Why

no-downgrade violations only tell you that trust regressed; they don't tell you from what, to what, or who published the offending version. That information is what actually decides whether a violation is a publish-pipeline regression we can wave through, an upstream maintainer change, or a supply-chain concern worth blocking on.

How

  • Registry access goes through pacote.packument(name, { where: workspaceDir, fullMetadata: true }). where makes pacote honor the audited workspace's .npmrc (scoped registries, _authToken / _auth), and fullMetadata: true is required because abbreviated packuments strip _npmUser, dist.attestations, and time.
  • Trust-evidence ranking and the publish-time-ordered prior-version walk mirror pnpm's getTrustEvidence / detectStrongestTrustEvidenceBeforeDate, but retain the version identity that pnpm discards.
  • Enrichment is best-effort: any per-package fetch/parse failure is recorded on violation.enrichmentError and the report falls back to pnpm's original hint, so one bad packument can't abort the batch.
  • Sequential per-violation: violation lists are small, pacote's packumentCache already memoizes per-name, and registries can rate-limit.

Dependencies

Adds pacote@^21 and @types/pacote to build-tools/packages/build-cli.

Validation

Run on the FluidFramework root surfaces 8 active violations (e.g. langchain, semver@5.7.2, etc.) with offender + prior-trusted lines populated. Behavior under registry/auth/network failure was exercised by forcing enrichment errors and confirming the fallback path.

Risk

Self-contained to a single flub check subcommand; no runtime/product code paths affected.

Reviewer Guidance

  • This script does a lot. I'd like suggestions to reduce the size by using libraries better-suited for checking package provenance.
  • Should the new functionality live in another module? (I think it should remain part of the trustPolicy command, it's just kind of cluttery.)

Extends the `flub check trustPolicy` command so that each `ERR_PNPM_TRUST_POLICY_VIOLATION` reported by pnpm is enriched with publish metadata fetched directly from the registry, making it possible to triage violations without manually digging through `npm view` output.

For every violation the report now shows:

- **Offender**: publish timestamp, publishing account, and trust evidence kind (`none` / `provenance` / `trustedPublisher`) — matching pnpm's own `prettyPrintTrustEvidence` formatting.
- **Prior trusted**: the most recent earlier-published version of the same package that carried trust evidence (the version pnpm's policy is comparing against).

Why

`no-downgrade` violations only tell you that trust regressed; they don't tell you _from what_, _to what_, or _who published the offending version_. That information is what actually decides whether a violation is a publish-pipeline regression we can wave through, an upstream maintainer change, or a supply-chain concern worth blocking on.

How

- Registry access goes through `pacote.packument(name, { where: workspaceDir, fullMetadata: true })`. `where` makes pacote honor the audited workspace's `.npmrc` (scoped registries, `_authToken` / `_auth`), and `fullMetadata: true` is required because abbreviated packuments strip `_npmUser`, `dist.attestations`, and `time`.
- Trust-evidence ranking and the publish-time-ordered prior-version walk mirror pnpm's `getTrustEvidence` / `detectStrongestTrustEvidenceBeforeDate`, but retain the version identity that pnpm discards.
- Enrichment is best-effort: any per-package fetch/parse failure is recorded on `violation.enrichmentError` and the report falls back to pnpm's original `hint`, so one bad packument can't abort the batch.
- Sequential per-violation: violation lists are small, pacote's `packumentCache` already memoizes per-name, and registries can rate-limit.

Dependencies

Adds `pacote@^21` and `@types/pacote` to `build-tools/packages/build-cli`.

Validation

Run on the FluidFramework root surfaces 8 active violations (e.g. `langchain`, `semver@5.7.2`, etc.) with offender + prior-trusted lines populated. Behavior under registry/auth/network failure was exercised by forcing enrichment errors and confirming the fallback path.

Risk

Self-contained to a single `flub check` subcommand; no runtime/product code paths affected.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

Hi! Thank you for opening this PR. Want me to review it?

Based on the diff (1333 lines, 4 files), I've queued these reviewers:

  • Correctness — logic errors, race conditions, lifecycle issues
  • Security — vulnerabilities, secret exposure, injection
  • API Compatibility — breaking changes, release tags, type design
  • Performance — algorithmic regressions, memory leaks
  • Testing — coverage gaps, hollow tests

How this works

  • Adjust the reviewer set by ticking/unticking boxes above. Reviewer toggles alone don't trigger anything.

  • Tick Start review below to dispatch the review fleet.

  • After review finishes, tick Start review again to request another run — it auto-resets after each dispatch.

  • This comment updates as new commits land; your reviewer selections are preserved.

  • Start review

@TommyBrosman TommyBrosman marked this pull request as ready for review May 7, 2026 18:59
Copilot AI review requested due to automatic review settings May 7, 2026 18:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Enhances flub check trustPolicy output by enriching pnpm trust-policy downgrade violations with registry publish metadata to make violations easier to triage.

Changes:

  • Add registry packument enrichment (publisher, publish time, trust evidence, and “prior trusted” version) for each trust-policy violation.
  • Track and report workspace “roots” that reach each violating name@version.
  • Update pnpm execution to buffer output (no live streaming) and add pacote dependency for registry access.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 5 comments.

File Description
build-tools/packages/build-cli/src/commands/check/trustPolicy.ts Adds violation enrichment via pacote, root attribution from pnpm list, and richer violation reporting.
build-tools/packages/build-cli/package.json Adds pacote / @types/pacote to support registry metadata fetch.
build-tools/packages/build-cli/docs/build-perf.md Updates CLI examples’ line continuation characters.
Files not reviewed (1)
  • build-tools/pnpm-lock.yaml: Language not supported

Comment on lines +748 to +771
const offenderTime = Date.parse(offenderTimeStr);
const offenderIsPrerelease = semver.prerelease(v.version) !== null;
let bestTime = -Infinity;
// Walk every published version: keep ones predating the offender
// that carry trust evidence, pick the most recently published.
// Mirrors pnpm's `detectStrongestTrustEvidenceBeforeDate` but
// retains the specific version (pnpm only returns the evidence kind).
for (const [cVersion, cTimeStr] of Object.entries(pkg.time ?? {})) {
if (cVersion === "created" || cVersion === "modified") continue;
const cManifest = versions[cVersion];
if (cManifest === undefined) continue;
if (!offenderIsPrerelease && semver.prerelease(cVersion) !== null) continue;
const cTime = Date.parse(cTimeStr);
if (Number.isNaN(cTime) || cTime >= offenderTime || cTime <= bestTime) continue;
const evidence = getTrustEvidence(cManifest);
if (evidence === "none") continue;
bestTime = cTime;
v.priorTrusted = {
version: cVersion,
publishedAt: cTimeStr,
publisher: cManifest._npmUser?.name,
evidence,
};
}
v.publisher = offender._npmUser?.name;
v.evidence = getTrustEvidence(offender);

const offenderTime = Date.parse(offenderTimeStr);
const rootSet = rootsByKey.get(key);
const roots =
rootSet === undefined || rootSet.size === 0 ? undefined : [...rootSet].sort();
violations.push({ name, version, ...reason, roots });
Comment thread build-tools/packages/build-cli/docs/build-perf.md Outdated
Comment thread build-tools/packages/build-cli/docs/build-perf.md Outdated
The 'What' commit picked up Windows-shell backtick line continuations from local oclif readme regeneration. Restore main's Linux-shell backslash continuations so CI's docs regeneration is clean.
When a trust-downgrade violation's offender or prior-trusted version carries provenance evidence, fetch the SLSA bundle from npm's attestations endpoint, verify it with sigstore.verify (Fulcio cert chain + Rekor inclusion + DSSE signature), and surface the source repo, commit, workflow, builder, run URL, and signer identity. Also flags any mismatch between the attested subject digest and the registry tarball's dist.integrity.

sigstore is loaded via createRequire against locally-declared interfaces because sigstore 4.x's transitive .d.ts graph fails typecheck against this repo's @types/node baseline.
Move the npm-attestations fetch, sigstore.verify call, in-toto statement parsing, dist.integrity comparison, ProvenanceDetails interface, and renderProvenanceDetails out of trustPolicy.ts into a dedicated module. trustPolicy.ts now only knows about ProvenanceDetails as an opaque type and calls fetchAndVerifyProvenance / renderProvenanceDetails through the module boundary.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants