Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
**Vulnerability:** Source Generator `NTypeForge.SourceGenerator` was always emitting `public static class` for its duck-typing extensions. If the type being ducked (`target`) was `internal`, this created a `CS0051: Inconsistent accessibility` compiler error and essentially violated the principle of least privilege, potentially leaking internal types into public scope.
**Learning:** Source Generators creating types meant to be used alongside user code should dynamically match the accessibility of their targets (or default to `internal` if appropriate for their use-case) to prevent scope leakage.
**Prevention:** Check `DeclaredAccessibility == Accessibility.Public` (or effective accessibility for nested types) on the target type, and emit `internal static class` instead of `public static class` when the target is not public.

## 2025-06-09 - [Scope Leakage] Robust Checking for Effective Public Accessibility
**Vulnerability:** The source generator's `IsEffectivelyPublic` logic failed to deeply inspect compound types such as arrays, pointers, and generic type arguments. Consequently, a public generic type parameterized with an internal type (e.g. `PublicGeneric<InternalClass>`) was incorrectly considered fully public. This could lead to scope leakage by emitting `public` extension classes for types that logically should be treated as `internal`, resulting in CS0050/CS0051 visibility errors when compiling generated code.
**Learning:** Checking type visibility in Roslyn requires more than just checking `DeclaredAccessibility` of a type and its `ContainingType`. Compound types wrap nested element types whose accessibilities must be recursively verified. In addition, array and pointer types report `Accessibility.NotApplicable`, which must be handled.
**Prevention:** `IsEffectivelyPublic` must recursively check `ElementType` for Arrays, `PointedAtType` for Pointers, and `TypeArguments` for Generic Types to ensure no internal types are inadvertently leaked.
19 changes: 18 additions & 1 deletion src/NTypeForge.SourceGenerator/CandidateAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,25 @@ private static bool IsEffectivelyPublic(ITypeSymbol type)
{
for (var current = type; current != null; current = current.ContainingType)
{
if (current.DeclaredAccessibility != Accessibility.Public) return false;
if (current.DeclaredAccessibility != Accessibility.Public && current.DeclaredAccessibility != Accessibility.NotApplicable) return false;
}

switch (type.TypeKind)
{
case TypeKind.Array:
return IsEffectivelyPublic(((IArrayTypeSymbol)type).ElementType);
case TypeKind.Pointer:
return IsEffectivelyPublic(((IPointerTypeSymbol)type).PointedAtType);
}
Comment on lines +102 to +108

if (type is INamedTypeSymbol namedType && namedType.IsGenericType)
{
foreach (var typeArg in namedType.TypeArguments)
{
if (!IsEffectivelyPublic(typeArg)) return false;
Comment on lines +112 to +114

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include containing generic arguments in publicness check

For nested constructed targets such as PublicOuter<InternalType>.Inner, this loop only checks the innermost type's TypeArguments while the loop above checks the containing type's DeclaredAccessibility only. The internal argument on the constructed containing type is therefore never visited, so TargetIsPublic can remain true and ProxyEmitter will still emit a public static class with an internal receiver type, reproducing the CS0050/CS0051 leak this patch is meant to prevent. Recurse through each ContainingType's type arguments as well before returning public.

Useful? React with 👍 / 👎.

}
}

return true;
}

Expand Down