It is possible to bypass the html filter by abusing the fact that <textarea> doesn't need to encode its content and <svg> is basically xml parsing mode.
So <svg><textarea></svg>... bypasses the filter easily. <textarea> can be replaced by any similar tags like <style> too. I found a similar XSS in Joplin recently using the same technique too.
As for the CSRF part, I noticed that it is possible to use method like OPTIONS, HEAD or TRACE to access /profile/edit, but couldn't find a way to send that with a HTTP body from browser.
So I abused the fact web.jctf.pro isn't in PSL, which means it is possible to set session from a samesite page like http://xssl.web.jctf.pro/.
But naively setting cookie using document.cookie = 'session=... ;path=/profile; domain=web.jctf.pro' doesn't work for some reason.
I presume that it is probably because xssl.web.jctf.pro sets a http:// cookie but the origin session cookie is Secure, so Chrome doesn't let you override it that way.
Fortunately, there is a interesting article Cookie Bugs - Smuggling & Injection describes what to do in this case.
The empty key behavior (document.cookie = '=a=b') creates a cookie with key being a empty string and a value being a=b, but it serializes to a=b in Cookie header.
According to Go's cookie parsing, we know it is spliting on ; to get multiple parts, and then split on = to get key and value.
So a=b will be seen as a cookie with key a and value b by Go, which is exactly what we want.