Skip to content

Unwrap AOP proxies in configprops endpoint serialization#50273

Open
zxuhan wants to merge 2 commits intospring-projects:mainfrom
zxuhan:gh-50231-configprops-aop-proxy-leak
Open

Unwrap AOP proxies in configprops endpoint serialization#50273
zxuhan wants to merge 2 commits intospring-projects:mainfrom
zxuhan:gh-50231-configprops-aop-proxy-leak

Conversation

@zxuhan
Copy link
Copy Markdown
Contributor

@zxuhan zxuhan commented May 1, 2026

The configprops actuator endpoint serializes each @ConfigurationProperties
bean using its bean-factory instance. When that instance is an AOP proxy
(commonly a CGLIB scoped proxy created by @RefreshScope, but the same
applies to any AOP proxy), the serializer also picks up the Advised
interface getters that the proxy exposes (targetSource, exposeProxy,
preFiltered).

With BufferingApplicationStartup configured, the targetSource walk reaches
beanFactory.applicationStartup.bufferedTimeline.events[…], inflating the
response by ~200 KB per refresh-scoped bean.

This change unwraps AOP proxies via Advised.getTargetSource().getTarget()
in safeSerialize before delegating to the serializer, so only the user-defined
configuration properties are exposed. The unwrap is wrapped in a try/catch so
that any failure to obtain the target falls back to the existing behaviour.

A test has been added that creates a ProxyFactory-based CGLIB proxy around a
@ConfigurationProperties bean and verifies that the resulting descriptor
contains only the user properties, not targetSource / exposeProxy /
preFiltered.

See gh-50231

The configprops endpoint serialized AOP proxies directly, leaking the
Advised interface getters (targetSource, exposeProxy, preFiltered) into
the response. With BufferingApplicationStartup configured, the
targetSource walk reached the entire startup-event buffer.

Unwrap to the proxy's target before handing it to the serializer so
only the user-defined configuration properties are exposed.

See spring-projectsgh-50231

Signed-off-by: zxuhan7 <zxuhan7@gmail.com>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label May 1, 2026
Copy link
Copy Markdown
Member

@wilkinsona wilkinsona left a comment

Choose a reason for hiding this comment

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

Thanks for the PR, @zxuhan. I've left a few comments for your consideration.

}

private @Nullable Object unwrapAopProxy(@Nullable Object bean) {
if (bean instanceof Advised advised) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A bean could have been proxied multiple times so a while loop would be better here.

}
}
catch (Exception ex) {
// Fall through and serialize the proxy
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should exclude the bean from the result rather than serializing the proxy.

private Map<String, @Nullable Object> safeSerialize(@Nullable Object bean, String prefix) {
try {
return new HashMap<>(this.serializer.serialize(bean));
return new HashMap<>(this.serializer.serialize(unwrapAopProxy(bean)));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the unwrapping call would be better done earlier. If it returns null (no target, getting the target fails, etc), we should skip the bean entirely.

- Move proxy unwrap out of safeSerialize and into describeBean
- Loop on Advised so chains of proxies are unwrapped
- Skip the bean when the target cannot be obtained instead of
  serializing the proxy

See spring-projectsgh-50231

Signed-off-by: zxuhan7 <zxuhan7@gmail.com>
@zxuhan
Copy link
Copy Markdown
Contributor Author

zxuhan commented May 1, 2026

Thanks for the review, @wilkinsona. Pushed 41a538f:

  • moved the unwrap into describeBean so it runs before safeSerialize,
  • looped on Advised to handle proxies-of-proxies,
  • return null from describeBean (and skip the entry in describeBeans) when the target can't be obtained.

Added two tests covering nested proxies and an unresolvable TargetSource.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants