{"slug": "with-ai", "title": "(WITH-AI ...)", "summary": "A developer who used AI to build a web service discovered that the LLM-generated code included sophisticated CSRF protection, complete with macros and detailed comments. The AI refactored the middleware to use functional programming principles and generated a dedicated CSRF module with token generation, validation, and exemption handling for certain endpoints.", "body_md": "I vibe coded my web site, not bothering to examine the code generated by the LLM, but giving it specifically directed prompts to generate a `secure` web service. I cracked open the code today to see how it did. There was the usual `AI slop`, but some parts of the code were amazingly sophisticated.\n\nAs part of my vibe coding, I explicitly made a pass where I asked\nthe AI to refactor the code to be more `functional` and adhere to\nfunctional programming principles. This turned out to produce some\nnice results. The AI refactored elements of the middleware to use\nsome `WITH-...`\n\nmacros that it had defined for itself to\nabstract out some of the common patterns. Let me show you some of\nwhat it was doing.\n\nCross-site request forgery (CSRF) is a common web security vulnerability. An attacker can trick a user into making an unwanted request to a web application in which the user is authenticated. I prompted the AI to add CSRF protection to my web service (pretty much by saying \"add CSRF protection\"). The AI generated a file specifically for CSRF protection. The file starts with this comment:\n\n```\n;; --- CSRF PROTECTION ---\n;;\n;; Every state-changing HTML <form method='POST'> in this application\n;; carries a per-session CSRF token (via CSRF-INPUT-HTML), and every\n;; corresponding :POST handler branch validates it (via\n;; WITH-CSRF-PROTECTION) before doing anything else. This defeats classic\n;; cross-site request forgery, where a malicious page tricks a logged-in\n;; user's browser into submitting a form to us: the attacker's page has no\n;; way to read or guess the token stashed in the victim's own session.\n;;\n;; JSON/fetch-based API endpoints (/api/login, /goog/chef, /lisp-p) and\n;; the Stripe webhook are intentionally exempted: they either predate any\n;; session state worth protecting, or already authenticate via other means\n;; (Stripe's webhook signature, the membership JWT + custom header that a\n;; cross-site <form> submission cannot forge).\n```\n\nThis comment isn't for me, it's for subsequent AI passes that will be working on the code. It explains the purpose of the CSRF protection and how it works. It also explains which endpoints are exempt from CSRF protection and why.\n\nThen the code starts with a function that generates a CSRF token and stores it in the user's session. The token is a secure random string large enough to be unguessable.\n\n```\n(defun csrf-token ()\n  \"Return this session's CSRF token, generating and storing one on first\nuse. Starts a session if one does not already exist, so this is safe to\ncall from a GET handler that is about to render a form.\"\n  (hunchentoot:start-session)\n  (or (hunchentoot:session-value :csrf-token)\n      (setf (hunchentoot:session-value :csrf-token)\n            (ironclad:byte-array-to-hex-string (ironclad:random-data 32)))))\n```\n\nNote how the docstring (written by the LLM) tells the LLM how to use the function elsewhere in the code. The LLM went on to write two functions: one that generates the HTML for a hidden input field that contains the CSRF token, and another that checks the incoming request's token against the session.\n\n```\n(defun csrf-input-html ()\n  \"A hidden <input> field carrying the current session's CSRF token, meant\nto be spliced into every POST <form> rendered by this application.\"\n  (format nil \"<input type='hidden' name='csrf-token' value='~A'>\" (csrf-token)))\n\n(defun csrf-token-valid-p ()\n  \"Check the incoming request's `csrf-token' POST parameter against the\nvalue stashed in the session by CSRF-TOKEN. Requests with no session, no\nstored token, or a missing/mismatched submitted token are rejected.\"\n  (let ((expected (hunchentoot:session-value :csrf-token))\n        (submitted (hunchentoot:post-parameter \"csrf-token\")))\n    (and expected submitted (string= expected submitted))))\n```\n\nIf the CSRF token is missing or invalid, the request is rejected with this response:\n\n```\n(defun csrf-forbidden-response ()\n  \"The 403 response returned in place of a POST handler's normal body when\nCSRF validation fails.\"\n  (setf (hunchentoot:return-code*) hunchentoot:+http-forbidden+)\n  \"<html><head><style>body { font-family: sans-serif; background: #111; color: #f00; padding: 2rem; }</style></head><body><h2>403 Forbidden</h2><p>Invalid or missing CSRF token. Please reload the page and try again.</p></body></html>\")\n```\n\nNow we need to wire up these primitives into the request handling.\n\n```\n(defun wrap-csrf-protected (thunk)\n  \"Return the result of calling THUNK (a zero-argument closure wrapping a\nPOST handler's guarded body) if the current request carries a valid CSRF\ntoken; otherwise return the 403 Forbidden response without calling THUNK.\nThis is the composable, higher-order form of WITH-CSRF-PROTECTION -- usable\ndirectly with FUNCTION:COMPOSE or other combinators in new code.\"\n  (if (csrf-token-valid-p)\n      (funcall thunk)\n      (csrf-forbidden-response)))\n\n(defmacro with-csrf-protection (&body body)\n  \"Wrap the body of a POST handler branch so it only executes if the\nrequest carries a valid CSRF token; otherwise responds 403 Forbidden. A\nthin macro over WRAP-CSRF-PROTECTED, preserving every existing call site.\"\n  `(wrap-csrf-protected (lambda () ,@body)))\n```\n\nThe AI used functional programming principles to write a\nhigher-order wrapper for the CSRF protection and a\nconvenience macro that wraps the body of a POST handler. It\ndocumented the functions and macro so that subsequent AI passes\nwould know how to use them. This is pretty sophisticated. Other\nparts of the code simply have to write ```\n(with-csrf-protection\n...)\n```\n\naround the body of a POST handler and the CSRF\nprotection is automatically applied.\n\nThe AI also went on to include a higher-order combinator for guarding code execution.\n\n```\n;; --- AUTHORIZATION GUARD COMBINATOR ---\n;;\n;; A single, audited shape for \"check X, else redirect Y\", replacing three\n;; ad hoc hand-rolled versions (REQUIRE-MEMBERSHIP-JWT/REQUIRE-WHEEL/\n;; REQUIRE-MEMBERSHIP-TIER in jwt.lisp, and REQUIRE-SESSION-WHEEL in\n;; admin.lisp). See FUNCTIONAL_REFACTOR.md Phase 3.\n\n(defun require-guard (check on-failure)\n  \"Generic authorization combinator. CHECK is a zero-argument thunk that\nreturns a non-NIL success value (e.g. JWT claims, or a wheel's username) or\nNIL to indicate failure. ON-FAILURE is a zero-argument thunk invoked (for\nside effect, typically a HUNCHENTOOT:REDIRECT) only when CHECK fails.\nReturns CHECK's success value, or NIL on failure -- callers should stop\nprocessing immediately on a NIL return, since ON-FAILURE has already sent\na response.\"\n  (or (funcall check)\n      (progn (funcall on-failure) nil)))\n```\n\nSeveral of the pages on jrm-code-project.com are protected by a membership JWT. The\nAI used this combinator to write authorization gates that check for the\npresence of a valid JWT and redirect to the login page if the JWT\nis missing or invalid. There are two ways to obtain a JWT. You can\neither log in manually and get a JWT in your browser, or you can use\nthe programmatic API to obtain a JWT by exchanging your long-lived\nAPI key for a short-lived JWT. The JWT encodes the user's\nmembership tier. A web page will\ncall `require-membership-tier`\n\nto check that the user has\nthe appropriate membership tier to access the page.\n\n```\n(defun require-membership-jwt (&optional (return-path (hunchentoot:request-uri*)))\n  \"Ensure the current request carries a valid, unexpired membership JWT.\nReturns the JWT claims alist if present and valid; otherwise redirects to\nthe login splash page (with a `next` breadcrumb pointing back at\nRETURN-PATH) and returns NIL. Callers of a JWT-protected page should check\nfor a NIL return and immediately stop processing, since REDIRECT has\nalready sent the response.\nSee the repository memory note: JWT-protected pages must redirect to the\nlogin splash page whenever the JWT is missing, malformed, or expired.\"\n  (require-guard\n   (lambda ()\n     (let ((token (hunchentoot:cookie-in *jwt-cookie-name*)))\n       (and token (decode-jwt token))))\n   (lambda () (redirect-to-login-with-breadcrumb return-path))))\n\n(defun require-membership-tier (minimum-tier &optional (return-path (hunchentoot:request-uri*)))\n  \"Ensure the current request carries a valid membership JWT whose tier meets\nor exceeds MINIMUM-TIER (\\\"CONS\\\", \\\"CADR\\\", or \\\"LAMBDA\\\"). Returns the JWT\nclaims alist on success; otherwise redirects (to login if the JWT is\nmissing/expired, or to the upgrade-required page if the tier is\ninsufficient) and returns NIL. Callers should check for a NIL return and\nimmediately stop processing, since REDIRECT has already sent the response.\"\n  (let ((claims (require-membership-jwt return-path)))\n    (and claims\n         (require-guard\n          (lambda () (and (tier-meets-minimum-p (cdr (assoc :tier claims)) minimum-tier) claims))\n          (lambda () (redirect-to-upgrade-required minimum-tier return-path))))))\n```\n\nThis isn't AI slop. The AI wrote some pretty good code here. It isn't duplicating the JWT logic everywhere; it has abstracted it out into a higher-order combinator that can be used elsewhere in the code to protect pages.\n\nAI code generation has come a long way in the past year.", "url": "https://wpnews.pro/news/with-ai", "canonical_source": "https://funcall.blogspot.com/2026/08/with-ai.html", "published_at": "2026-08-23 16:57:16+00:00", "updated_at": "2026-08-23 17:13:40.557870+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-tools", "ai-products", "developer-tools"], "entities": ["Hunchentoot", "Ironclad", "Stripe"], "alternates": {"html": "https://wpnews.pro/news/with-ai", "markdown": "https://wpnews.pro/news/with-ai.md", "text": "https://wpnews.pro/news/with-ai.txt", "jsonld": "https://wpnews.pro/news/with-ai.jsonld"}}