• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

orion-ecs / keen-eye / 20667926793

02 Jan 2026 10:25PM UTC coverage: 80.864% (-0.2%) from 81.038%
20667926793

push

github

tyevco
feat(editor): Add plugin security and sandboxing system

Implement comprehensive plugin security infrastructure with:

Phase 1 - Code Analysis & Signing:
- AssemblyAnalyzer: Static IL analysis using System.Reflection.Metadata
- Detects reflection, unsafe code, P/Invoke, file/network access patterns
- PluginSignatureVerifier: Validates assembly signatures
- TrustedPublisherStore: Manages trusted signing keys
- PluginSecurityManager: Coordinates security checks before plugin load
- SecurityConfiguration: Configurable analysis modes (WarnOnly/Block)

Phase 2 - Permission System:
- PluginPermission: 64-bit flags enum with 24 permissions + composites
- PermissionManager: Grants/revokes/validates per-plugin permissions
- SecurePluginContext: Permission-aware IEditorContext wrapper
- Capability-to-permission mapping (Menu→MenuAccess, Panel→PanelAccess)
- Async permission request flow with user consent handler
- Persistent storage at ~/.keeneyes/plugin-permissions.json

Integration:
- PluginLoader runs security checks before assembly loading
- EditorPluginManager uses SecurePluginContext when enabled
- PluginManifest supports security and permissions sections

Closes #677

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

5309 of 6365 branches covered (83.41%)

Branch coverage included in aggregate %.

774 of 1050 new or added lines in 16 files covered. (73.71%)

4 existing lines in 3 files now uncovered.

34088 of 42355 relevant lines covered (80.48%)

1.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.2
/src/KeenEyes.Assets/Systems/AssetResolutionSystem.cs
1
using System.Collections.Concurrent;
2

3
namespace KeenEyes.Assets;
4

5
/// <summary>
6
/// System that resolves <see cref="AssetRef{T}"/> components to loaded asset handles.
7
/// </summary>
8
/// <remarks>
9
/// <para>
10
/// This system runs in the EarlyUpdate phase and automatically loads assets referenced
11
/// by <see cref="AssetRef{T}"/> components. Once an asset is loaded, the component's
12
/// internal handle ID is set, and <see cref="AssetRef{T}.IsResolved"/> returns true.
13
/// </para>
14
/// <para>
15
/// Assets are loaded asynchronously with <see cref="LoadPriority.Normal"/> priority.
16
/// The system uses async loading to avoid frame hitches from loading large assets.
17
/// Pending load tasks are tracked and observed to prevent unobserved exceptions.
18
/// </para>
19
/// </remarks>
20
public sealed class AssetResolutionSystem : ISystem
21
{
22
    private IWorld? world;
23
    private AssetManager? assetManager;
24

25
    // Track pending loads with their tasks to observe exceptions
26
    private readonly ConcurrentDictionary<string, Task> pendingLoads = new();
1✔
27

28
    // Completed tasks to process on next update
29
    private readonly ConcurrentQueue<string> completedPaths = new();
1✔
30

31
    /// <inheritdoc />
32
    public bool Enabled { get; set; } = true;
1✔
33

34
    /// <inheritdoc />
35
    public void Initialize(IWorld world)
36
    {
37
        this.world = world;
1✔
38

39
        if (!world.TryGetExtension<AssetManager>(out assetManager))
1✔
40
        {
41
            // No asset manager - system will do nothing
42
            Enabled = false;
1✔
43
        }
44
    }
1✔
45

46
    /// <inheritdoc />
47
    public void Update(float deltaTime)
48
    {
49
        if (assetManager == null || world == null)
1✔
50
        {
51
            return;
1✔
52
        }
53

54
        // Process completed tasks first
55
        ProcessCompletedTasks();
1✔
56

57
        // Resolve texture assets
58
        ResolveAssets<TextureAsset>();
1✔
59

60
        // Resolve audio assets
61
        ResolveAssets<AudioClipAsset>();
1✔
62

63
        // Resolve mesh assets
64
        ResolveAssets<MeshAsset>();
1✔
65

66
        // Resolve raw assets
67
        ResolveAssets<RawAsset>();
1✔
68
    }
1✔
69

70
    private void ProcessCompletedTasks()
71
    {
72
        // Clean up completed tasks from the dictionary
73
        while (completedPaths.TryDequeue(out var path))
1✔
74
        {
75
            pendingLoads.TryRemove(path, out _);
1✔
76
        }
1✔
77
    }
1✔
78

79
    private void ResolveAssets<T>() where T : class, IDisposable
80
    {
81
        foreach (var entity in world!.Query<AssetRef<T>>())
1✔
82
        {
83
            ref var assetRef = ref world.Get<AssetRef<T>>(entity);
1✔
84

85
            // Skip if already resolved or no path set
86
            if (assetRef.IsResolved || !assetRef.HasPath)
1✔
87
            {
88
                continue;
89
            }
90

91
            // Check if already loaded
92
            if (assetManager!.IsLoaded(assetRef.Path))
1✔
93
            {
94
                // Asset is loaded, resolve the reference
95
                var handle = assetManager.Load<T>(assetRef.Path);
1✔
96
                assetRef.HandleId = handle.Id;
1✔
97
                continue;
1✔
98
            }
99

100
            // Check if load is pending
101
            if (pendingLoads.ContainsKey(assetRef.Path))
1✔
102
            {
103
                continue;
104
            }
105

106
            // Start async load and track the task
107
            var loadTask = LoadAssetAsync<T>(assetRef.Path);
1✔
108
            pendingLoads.TryAdd(assetRef.Path, loadTask);
1✔
109
        }
110
    }
1✔
111

112
    private async Task LoadAssetAsync<T>(string path) where T : class, IDisposable
113
    {
114
        try
115
        {
116
            await assetManager!.LoadAsync<T>(path, LoadPriority.Normal);
1✔
117
        }
1✔
118
        catch (Exception ex)
×
119
        {
120
            // Invoke error callback if configured
121
            assetManager!.OnLoadError?.Invoke(path, ex);
×
122
        }
×
123
        finally
124
        {
125
            // Queue for removal on next update
126
            completedPaths.Enqueue(path);
1✔
127
        }
128
    }
1✔
129

130
    /// <inheritdoc />
131
    public void Dispose()
132
    {
133
        // Wait for all pending loads to complete to ensure no unobserved exceptions
134
        var tasks = pendingLoads.Values.ToArray();
1✔
135
        if (tasks.Length > 0)
1✔
136
        {
UNCOV
137
            Task.WhenAll(tasks).ConfigureAwait(false).GetAwaiter().GetResult();
×
138
        }
139
        pendingLoads.Clear();
1✔
140
    }
1✔
141
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc