Attribute command executions to trusted plugin scripts (#35020)

## What changed

- Resolve shell and unified-exec commands against the trusted plugin roots loaded for each turn.
- Add optional `pluginId` and safe plugin-relative `scriptPath` fields to command execution items and legacy execution events, and propagate them through app-server notifications.
- Include the attribution in command execution analytics while rejecting absolute, unsafe, and unattributed script paths.

## Testing

- Cover attribution for cached curated and remote plugin scripts from command execution through core and app-server events.
- Verify analytics serialization and unsafe-path filtering.

GitOrigin-RevId: 02fac3a233284ccfc6642fa502a95f1881dba83d
This commit is contained in:
Kyle Brown authored and copyberry committed 2026-07-23 22:31:53 +00:00
1 parent 5bdbd3ee90
commit 84fa68b429
52 files changed
+858 -32

No files matched your search

+17
View File
@@ -1,6 +1,8 @@
use super::*;
use crate::environment_selection::TurnEnvironmentSnapshot;
use crate::shell_snapshot::ShellSnapshotFile;
use codex_core_plugins::PluginCommandAttribution;
use codex_core_plugins::TrustedPluginRoots;
use codex_core_skills::HostSkillsSnapshot;
use codex_file_system::FileSystemSandboxContext;
use codex_model_provider::SharedModelProvider;
@@ -173,6 +175,16 @@ impl TurnContext {
}
}
pub(crate) fn plugin_attribution_for_command(
&self,
command: &[String],
cwd: &AbsolutePathBuf,
) -> Option<PluginCommandAttribution> {
self.extension_data
.get::<TrustedPluginRoots>()?
.resolve_attribution(command, cwd)
}
pub(crate) fn permission_profile(&self) -> PermissionProfile {
self.permission_profile.clone()
}
@@ -742,6 +754,10 @@ impl Session {
.plugins_manager
.plugins_for_config(&plugins_input)
.await;
let trusted_plugin_roots = TrustedPluginRoots::from_plugin_load_outcome(
&plugin_outcome,
per_turn_config.codex_home.as_path(),
);
let effective_skill_roots = plugin_outcome.effective_plugin_skill_roots();
let plugin_skill_snapshots = self
.services
@@ -785,6 +801,7 @@ impl Session {
sub_id,
skills_snapshot,
);
turn_context.extension_data.insert(trusted_plugin_roots);
turn_context.realtime_active = self.conversation.running_state().await.is_some();
if let Some(final_schema) = final_output_json_schema {