fix(core): fix TRUST_PARENT rule precedence in folder-trust resolution - #28701
fix(core): fix TRUST_PARENT rule precedence in folder-trust resolution#28701herdiyana256 wants to merge 1 commit into
Conversation
LoadedTrustedFolders.isPathTrusted() picks the winning trust rule for a given location by "longest match wins", intending the most specific configured rule to take precedence (confirmed by the existing "should handle isPathTrusted with longest match" test). For a TRUST_PARENT rule, the boundary it actually applies to is dirname(rulePath) -- one directory level shallower than the rule's own configured path -- but the specificity comparison used rulePath.length (the rule's own, deeper path) rather than the effective boundary's length. This overstates a TRUST_PARENT rule's specificity by the length of the child segment it explicitly strips away, letting it silently outrank a genuinely more specific sibling rule (e.g. an explicit DO_NOT_TRUST) that sits exactly at that parent boundary, whenever the TRUST_PARENT rule's own configured path string happens to be longer. "Trust parent folder" is one of only three choices in the standard folder-trust dialog shown the first time any folder is opened, so having a TRUST_PARENT rule recorded under a common parent directory is an ordinary, frequently-chosen outcome, not a contrived edge case. Fix: compare using the normalized effective path's length instead of the raw configured rulePath length, so specificity reflects the boundary a rule actually applies to.
|
📊 PR Size: size/S
|
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a bug in the folder-trust resolution mechanism where TRUST_PARENT rules were incorrectly prioritized due to an inaccurate specificity calculation. By using the normalized effective path length, the system now correctly identifies the intended boundary for trust rules, ensuring that more specific sibling rules are respected and preventing unintended trust grants. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request fixes a specificity issue in the trust utility by comparing effective path lengths instead of raw rule path lengths, and adds a corresponding regression test. The reviewer identified a critical security concern where rules resolving to the same effective path length could lead to non-deterministic trust resolution, and provided a code suggestion to enforce a secure-by-default tie-breaker prioritizing DO_NOT_TRUST.
| if (normalizedEffectivePath.length > longestMatchLen) { | ||
| longestMatchLen = normalizedEffectivePath.length; | ||
| longestMatchTrust = trustLevel; | ||
| } |
There was a problem hiding this comment.
When two rules resolve to the exact same effective path boundary (for example, an explicit DO_NOT_TRUST on /home/user/project and a TRUST_PARENT on /home/user/project/.gemini), they will have the exact same normalizedEffectivePath.length.
Under the current implementation, the rule that is processed first in Object.entries(configToUse) will win because of the strict > comparison. Since object key ordering can be non-deterministic or depend on insertion order, this could lead to a security bypass where a folder explicitly marked as DO_NOT_TRUST is resolved as trusted.
To enforce a "secure by default" methodology and make the resolution deterministic, we should add a tie-breaker for equal lengths where DO_NOT_TRUST always takes precedence, and explicit TRUST_FOLDER takes precedence over inherited TRUST_PARENT.
if (normalizedEffectivePath.length > longestMatchLen) {
longestMatchLen = normalizedEffectivePath.length;
longestMatchTrust = trustLevel;
} else if (normalizedEffectivePath.length === longestMatchLen) {
if (
trustLevel === TrustLevel.DO_NOT_TRUST ||
(trustLevel === TrustLevel.TRUST_FOLDER &&
longestMatchTrust === TrustLevel.TRUST_PARENT)
) {
longestMatchTrust = trustLevel;
}
}References
- Security checks should be implemented in a 'fail-closed' manner. If there is ambiguity or a tie in trust resolution, the system should default to the most secure option (e.g., rejecting trust).
Summary
LoadedTrustedFolders.isPathTrusted()picks the winning trust rule for a given location by "longest match wins" -- intended to make the most specific configured rule take precedence (this is exactly what the existingshould handle isPathTrusted with longest matchtest verifies). For aTRUST_PARENTrule, the boundary it actually applies to isdirname(rulePath)-- one directory level shallower than the rule's own configured path -- but the specificity comparison usedrulePath.length(the rule's own, deeper path) instead of the effective boundary's length.This overstates a
TRUST_PARENTrule's specificity by the length of the child path segment it explicitly strips away viadirname(), letting it silently outrank a genuinely more specific sibling rule (e.g. an explicitDO_NOT_TRUST) that sits exactly at that parent boundary, whenever theTRUST_PARENTrule's own configured path string happens to be longer.Verified with the real
LoadedTrustedFoldersclass (not a reimplementation):"Trust parent folder" is one of only three choices in the standard folder-trust dialog shown the first time any folder is opened (
FolderTrustDialog.tsx), so ending up with aTRUST_PARENTrule recorded under a common parent directory is an ordinary, frequently-chosen outcome -- not a contrived configuration. Once such a rule exists anywhere under a shared parent, an unrelated sibling folder the user has explicitly markedDO_NOT_TRUST(or left unconfigured, if the rule's path length exceeds a siblingTRUST_FOLDER's -- less likely but structurally the same class of bug) can silently resolve as trusted, enabling project-sourced hooks and auto-connecting project-configured MCP servers against a folder the user never actually trusted.Fix
Compare using the normalized effective path's length instead of the raw configured
rulePathlength, so specificity reflects the boundary a rule actually applies to rather than the length of an arbitrary child segmentTRUST_PARENTstrips away. No behavior change forTRUST_FOLDER/DO_NOT_TRUSTrules, whereeffectivePath === rulePathalready.Test plan
trust.test.ts), confirming the explicitDO_NOT_TRUSTsibling now correctly wins, while unrelated siblings still correctly inherit theTRUST_PARENTgrant.npx vitest run --root packages/core packages/core/src/utils/trust.test.ts-- 13/13 passing (was 12/12 pre-fix, +1 new)npm run typecheck --workspace=@google/gemini-cli-corenpx eslinton both changed files -- clean