How Project Trust and Project Identity Work in Pi Web: Security and Session Grouping Explained
Project trust and project identity in Pi Web work together to gate the execution of local resources through explicit user consent while ensuring sessions group correctly across different filesystem paths, implemented via ProjectTrustStore and projectIdentityKey respectively.
The agegr/pi-web codebase implements a dual-layer system to handle potentially unsafe project-local code and cross-platform session organization. Project trust determines whether extensions, custom settings, and skills may execute, while project identity provides a stable, normalized key for grouping sessions regardless of OS-specific path variations.
Project Trust: Gating Local Resource Execution
Pi Web treats any repository containing trust-requiring resources as potentially unsafe. These resources include .pi/extensions directories, project-wide .pi/settings.json entries, or .agents/skills folders. The system stores trust decisions in the user's ~/.pi/agent directory via the SDK's ProjectTrustStore.
Detecting Trust Requirements
The getProjectTrustStatus function in lib/project-trust.ts checks for gated resources and returns whether the project requires trust and whether it has been granted:
// getProjectTrustStatus → lines 4-13
export function getProjectTrustStatus(cwd: string, agentDir: string): ProjectTrustStatus {
const requiresTrust = Boolean(cwd) && hasTrustRequiringProjectResources(cwd);
if (!requiresTrust) return { requiresTrust: false, trusted: true };
const trustStore = new ProjectTrustStore(agentDir);
return {
requiresTrust: true,
trusted: trustStore.get(cwd) === true,
};
}
If hasTrustRequiringProjectResources finds no gated files, the project is implicitly trusted. Otherwise, the function queries the ProjectTrustStore for the current working directory.
Granting and Revoking Trust
The trustProject function writes the user's consent to the store:
// trustProject → lines 15-21
export function trustProject(cwd: string, agentDir: string): ProjectTrustStatus {
const status = getProjectTrustStatus(cwd, agentDir);
if (!status.requiresTrust) return status;
new ProjectTrustStore(agentDir).set(cwd, true);
return { requiresTrust: true, trusted: true };
}
Once persisted, subsequent calls to getProjectTrustStatus return trusted: true for that path.
Trust Integration in the Session Lifecycle
When an AgentSession starts, Pi Web supplies trust gating through projectTrustReloadOptions in lib/project-trust.ts:
// projectTrustReloadOptions → lines 40-48
export function projectTrustReloadOptions(
cwd: string,
agentDir: string,
): { resolveProjectTrust: () => Promise<boolean> } | undefined {
const status = getProjectTrustStatus(cwd, agentDir);
if (!status.requiresTrust) return undefined;
const trustStore = new ProjectTrustStore(agentDir);
return { resolveProjectTrust: async () => trustStore.get(cwd) === true };
}
The rpc-manager.ts file uses these options when constructing a DefaultResourceLoader. If resolveProjectTrust returns false, extensions remain dormant. After every reload, syncProjectTrust synchronizes the flag with the session's SettingsManager so UI components can display trust status:
// syncProjectTrust → lines 60-63
private syncProjectTrust(): void {
const status = getProjectTrustStatus(this.cwd, getAgentDir());
this.inner.settingsManager.setProjectTrusted(status.trusted);
}
Project Identity: Stable Grouping Across Filesystems
Sessions must group logically by project even when filesystem representations differ. Windows paths are case-insensitive and may contain trailing separators, while Git worktrees can reference the same repository through different physical directories.
Normalizing Path Variations
The projectIdentityKey function in lib/project-identity.ts generates a stable identity string:
// projectIdentityKey → lines 12-26
export function projectIdentityKey(
projectRoot: string,
platform: NodeJS.Platform = process.platform,
): string {
if (!projectRoot) return projectRoot;
const pathApi = platform === "win32" ? path.win32 : path.posix;
const normalized = pathApi.normalize(projectRoot);
const rootLength = pathApi.parse(normalized).root.length;
let end = normalized.length;
while (end > rootLength && normalized[end - 1] === pathApi.sep) end--;
const withoutTrailingSeparators = normalized.slice(0, end);
return platform === "win32"
? withoutTrailingSeparators.toLowerCase()
: withoutTrailingSeparators;
}
This function handles three normalization steps:
- Path normalization using the appropriate platform API
- Trailing separator removal while preserving the root (e.g.,
C:\) - Case folding for Windows platforms (
toLowerCase())
Identity in Session Management
The session-reader.ts file attaches projectKey to every SessionInfo object:
// attachSessionProjectInfo → lines 29-33
const projectRoot = project?.projectRoot ?? session.cwd;
return {
...session,
projectRoot,
projectKey: projectIdentityKey(projectRoot),
...(project?.isWorktree && project.branch ? { worktreeBranch: project.branch } : {}),
};
The UI uses this key for sidebar grouping rather than raw paths.
Identity in API Responses
The worktree API in app/api/worktrees/route.ts exposes projectKey to clients:
// worktrees route → lines 44-46
return NextResponse.json({
projectRoot: project.projectRoot,
projectKey: projectIdentityKey(project.projectRoot),
…
});
This allows client-side caching per logical project rather than per physical path.
Interaction Between Trust and Identity
When a user opens a repository, Pi Web executes a two-phase process:
- Identity Resolution:
projectIdentityKeyderives a stable key so the UI places the session in the correct project bucket, regardless of worktree path variations. - Trust Evaluation: If gated resources exist, the UI displays a "Trust required" prompt. The trust store is keyed by the raw cwd, while the UI groups by projectKey.
- Inheritance: Once the user clicks "Trust",
trustProjectpersists the decision. Subsequent sessions for any path resolving to the same project identity automatically inherit the trusted state because the store lookup uses the specific path while the UI presentation uses the normalized identity.
This architecture ensures that trusting a project in one worktree effectively trusts it for all worktrees of the same repository.
Summary
- Project trust gates execution of
.pi/extensions,.pi/settings.json, and.agents/skillsviaProjectTrustStoreinlib/project-trust.ts. getProjectTrustStatuschecks for gated resources;trustProjectwrites consent to~/.pi/agent.- Project identity provides OS-agnostic grouping keys via
projectIdentityKeyinlib/project-identity.ts, handling Windows case-insensitivity and trailing separators. rpc-manager.tssynchronizes trust status into running sessions, whilesession-reader.tsandapp/api/worktrees/route.tsconsume identity keys for UI grouping.- Trust decisions persist across worktrees because the store keys by raw path while the UI groups by normalized identity.
Frequently Asked Questions
How does Pi Web decide if a project requires trust?
Pi Web scans the repository root for trust-requiring resources using hasTrustRequiringProjectResources. If the project contains .pi/extensions, project-wide .pi/settings.json entries, or .agents/skills directories, getProjectTrustStatus returns requiresTrust: true and checks the ProjectTrustStore for user consent.
What happens if I decline to trust a project?
If resolveProjectTrust returns false in the projectTrustReloadOptions callback, the DefaultResourceLoader loads extensions in a dormant state. The AgentSession treats the project as untrusted, and local skills or custom settings will not execute, though the session remains functional for basic operations.
Why does Pi Web use a project identity key instead of the raw file path?
Raw filesystem paths fail to group sessions correctly across platform differences. Windows paths vary by case and may include trailing backslashes, while Git worktrees create multiple physical directories for one logical repository. The projectIdentityKey function normalizes these variations into a stable string, ensuring the sidebar groups all worktrees of the same project under a single entry.
Where is the trust status stored on disk?
The ProjectTrustStore persists the trust boolean in the user's ~/.pi/agent directory, keyed by the canonical working directory path. This location is separate from the project repository, allowing trust decisions to persist across git operations or directory moves as long as the path remains consistent.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →