coordinate-components — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited coordinate-components (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 7 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 7 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Read AGENTS.md at the workspace root to learn the project's conventions before making changes.
| Need | Mechanism | When to use |
|---|---|---|
| Subtree (same render mode) | CascadingValue component | Theme, layout config within a layout |
| App-wide (all render modes) | CascadingValueSource<T> via DI | Current user, feature flags, theme shared globally |
| Mutable shared state within a circuit | Scoped service + Action event | Shopping cart, notification count, selected filters |
For parent→child one level: use [Parameter] / EventCallback (see author-component skill). For persisting state across prerender→interactive: see support-prerendering skill.
CascadingValueSource<T> (Step 4)Program.cs with AddCascadingValue(...) and isFixed: false[CascadingParameter] in child componentsNotifyChangedAsync(newValue) — never page reloadStateHasChanged from background threads in InvokeAsyncIDisposable — dispose timers, cancel tokens, unsubscribe eventsWrap a subtree with <CascadingValue> to flow data to all descendants without passing it through every intermediate component.
@* In a layout or parent component *@
<CascadingValue Value="theme">
@Body
</CascadingValue>
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-primary" };
}Consume in any descendant:
[CascadingParameter]
private ThemeInfo? Theme { get; set; }Rules:
Name: <CascadingValue Value="primary" Name="PrimaryTheme">...</CascadingValue> [CascadingParameter(Name = "PrimaryTheme")]
private ThemeInfo? Primary { get; set; }IsFixed="true" when the value never changes — avoids subscription overhead.<CascadingValue> in a static SSR parent is invisible to interactive children. See Step 6.Register a CascadingValueSource<T> in DI when the value must be available to all components regardless of render mode.
// Program.cs
builder.Services.AddCascadingValue(sp =>
{
var theme = new ThemeInfo { ButtonClass = "btn-primary" };
return new CascadingValueSource<ThemeInfo>(theme, isFixed: false);
});Consume identically to Step 3:
[CascadingParameter]
private ThemeInfo? Theme { get; set; }To update and notify subscribers, either mutate the existing object or replace it:
@* Component that changes the theme *@
@inject CascadingValueSource<ThemeInfo> ThemeSource
<button @onclick="ToggleDarkMode">Toggle theme</button>
@code {
private bool isDark;
private async Task ToggleDarkMode()
{
isDark = !isDark;
// Replace the value entirely:
var newTheme = new ThemeInfo { ButtonClass = isDark ? "btn-dark" : "btn-primary" };
await ThemeSource.NotifyChangedAsync(newTheme);
}
}NotifyChangedAsync() (no argument) also works — mutate the object and then call it. NotifyChangedAsync(newValue) replaces the value and notifies in one step.
Update protocol: Whenever shared state changes, the component that changes it MUST inject CascadingValueSource<T> and call NotifyChangedAsync(). This is the only mechanism that triggers re-rendering in all [CascadingParameter] subscribers. Without this call, no subscribers update. Do not use NavigationManager.Refresh() or page reloads as a substitute.
Rules:
isFixed: false enables change notifications. isFixed: true is better for truly static values (feature flags).<CascadingValue>.NotifyChangedAsync re-renders ALL subscribers regardless of which property changed. Don't put all app state into one cascaded type..Client Program.cs. The type must be in a shared assembly.For mutable shared state that multiple components read and write (shopping cart, notification count, filters), use a scoped service with an event for change notification.
Define the service:
public class CartState
{
private readonly List<CartItem> _items = [];
public IReadOnlyList<CartItem> Items => _items;
public int Count => _items.Count;
public event Action? OnChange;
public void Add(CartItem item)
{
_items.Add(item);
OnChange?.Invoke();
}
public void Remove(CartItem item)
{
_items.Remove(item);
OnChange?.Invoke();
}
}Register as scoped:
builder.Services.AddScoped<CartState>();Subscribe in components:
@inject CartState Cart
@implements IDisposable
<span class="badge">@Cart.Count</span>
@code {
protected override void OnInitialized()
{
Cart.OnChange += StateHasChanged;
}
public void Dispose()
{
Cart.OnChange -= StateHasChanged;
}
}The simple Action OnChange pattern works when the event fires from the Blazor sync context (button click → Cart.Add(…)). If the event fires from outside the sync context (timer, background task, SignalR hub), wrap in InvokeAsync:
private Action? _handler;
protected override void OnInitialized()
{
_handler = () => InvokeAsync(StateHasChanged);
Cart.OnChange += _handler;
}
public void Dispose() => Cart.OnChange -= _handler;Store the delegate in a field so you can unsubscribe the exact same instance.
A <CascadingValue> placed in a static SSR layout (MainLayout.razor when the layout renders statically) will not reach interactive children. The interactive component sees null for the cascading parameter.
Fix: Use CascadingValueSource<T> registered in DI (Step 4) or a scoped service (Step 5). Both cross boundaries because DI services are resolved per-circuit, not from the component tree.
| Lifetime | Server | WebAssembly |
|---|---|---|
| Scoped | Per circuit (per user connection) | Per browser tab |
| Singleton | Shared across ALL users | Per browser tab (safe) |
| Transient | New instance per injection | New instance per injection |
On Server, never store user-specific state in a singleton — every user's circuit shares the same singleton. One user's cart leaks into another's. Use AddScoped<T>().
On WebAssembly, singletons are per-tab and safe. But code meant for both Server and WebAssembly (Auto mode) must use scoped.
State services must be defined in the .Client project or a shared assembly — they cannot reference server-only types. Register the service in both Program.cs files. State created during prerender does not survive the switch to the interactive runtime. Use the support-prerendering skill's [PersistentState] pattern to carry state across.
NotifyChangedAsync re-renders ALL subscribers on every change. Separate concerns into distinct types (ThemeState, CartState, UserPreferences).Dispose on event subscriptions causes memory leaks that grow per-circuit.CascadingValueSource<T> or scoped services.CascadingValueSource<T> and call NotifyChangedAsync(newValue) to push updates to all [CascadingParameter] subscribers without a page reload.InvokeAsync. The framework throws InvalidOperationException: The current thread is not associated with the Dispatcher.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.