Index: trac/web/api.py
===================================================================
--- trac/web/api.py	(revision 4127)
+++ trac/web/api.py	(working copy)
@@ -350,7 +350,7 @@
             content_type = 'text/plain'
             data = str(self.hdf)
         else:
-            data = self.hdf.render(template)
+            data = self.hdf.render(template, self.form_token)
 
         self.send_response(status)
         self.send_header('Cache-control', 'must-revalidate')
Index: trac/web/clearsilver.py
===================================================================
--- trac/web/clearsilver.py	(revision 4127)
+++ trac/web/clearsilver.py	(working copy)
@@ -14,6 +14,8 @@
 #
 # Author: Christopher Lenz <cmlenz@gmx.de>
 
+from HTMLParser import HTMLParser
+
 from trac.core import TracError
 from trac.util.html import Markup, Fragment, escape
 from trac.util.text import to_unicode
@@ -274,7 +276,7 @@
         cs.parseStr(string)
         return cs
 
-    def render(self, template):
+    def render(self, template, form_token=None):
         """Render the HDF using the given template.
 
         The template parameter can be either an already parse neo_cs.CS
@@ -286,9 +288,60 @@
             import neo_cs
             template = neo_cs.CS(self.hdf)
             template.parseFile(filename)
-        return template.render()
 
+        if form_token:
+            from cStringIO import StringIO
+            out = StringIO()
+            injector = FormTokenInjector(form_token, out)
+            injector.feed(template.render())
+            return out.getvalue()
+        else:
+            return template.render()
 
+
+class FormTokenInjector(HTMLParser):
+    """Identify and protect forms from CSRF attacks
+
+    This filter works by adding a input type=hidden field to POST forms.
+    """
+    def __init__(self, form_token, out):
+        HTMLParser.__init__(self)
+        self.out = out
+        self.token = form_token
+
+    def handle_starttag(self, tag, attrs):
+        self.out.write(self.get_starttag_text())
+        if tag.lower() == 'form':
+            for name, value in attrs:
+                if name.lower() == 'method' and value.lower() == 'post':
+                    self.out.write('<input type="hidden" name="__FORM_TOKEN"'
+                                   ' value="%s"/>' % self.token)
+                    
+    def handle_startendtag(self, tag, attrs):
+        self.out.write(self.get_starttag_text())
+        
+    def handle_charref(self, name):
+        self.out.write('&#%s;' % name)
+
+    def handle_entityref(self, name):
+        self.out.write('&%s;' % name)
+
+    def handle_comment(self, data):
+        self.out.write('<!--%s-->' % data)
+
+    def handle_decl(self, data):
+        self.out.write('<!--%s-->' % data)
+
+    def handle_pi(self, data):
+        self.out.write('<?%s>' % data)
+
+    def handle_data(self, data):
+        self.out.write(data)
+
+    def handle_endtag(self, tag):
+        self.out.write('</' + tag + '>')
+
+
 if __name__ == '__main__':
     import doctest, sys
     doctest.testmod(sys.modules[__name__])
Index: trac/web/main.py
===================================================================
--- trac/web/main.py	(revision 4127)
+++ trac/web/main.py	(working copy)
@@ -189,6 +189,7 @@
                 req.authname = self.authenticate(req)
                 req.perm = PermissionCache(self.env, req.authname)
                 req.session = Session(self.env, req)
+                req.form_token = self._get_form_token(req)
             except:
                 anonymous_request = True
                 early_error = sys.exc_info()
@@ -221,6 +222,15 @@
         try:
             try:
                 try:
+                    # Protect against CSRF attacks.
+                    # We can only block against such attacks if the user
+                    # is logged in or if we have an incoming session cookie.
+                    if (req.method == 'POST' and
+                        req.args.get('__FORM_TOKEN') != req.form_token and
+                        (req.incookie.has_key('trac_auth') or
+                         req.incookie.has_key('trac_session'))):
+                        raise TracError('Missing or invalid form token')
+
                     resp = chosen_handler.process_request(req)
                     if resp:
                         template, content_type = \
@@ -257,7 +267,22 @@
                                                             content_type)
         return template, content_type
 
+    def _get_form_token(self, req):
+        """Used to protect against CSRF.
 
+        The 'trac_auth' cookie is a good and strong shared secret, only
+        known by the user it belongs to and Trac itself.
+
+        The session id is our second best option, not as reliable since
+        it will change on each request if the user has cookies disabled in
+        his/her browser.
+        """
+        if req.incookie.has_key('trac_auth'):
+            return req.incookie['trac_auth'].value
+        else:
+            return req.session.sid
+
+
 def dispatch_request(environ, start_response):
     """Main entry point for the Trac web interface.
     

