diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+The following license covers this documentation, and the source code, except
+where otherwise indicated.
+
+Copyright 2010, Michael Snoyman. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,7 @@
+#!/usr/bin/env runhaskell
+
+> module Main where
+> import Distribution.Simple
+
+> main :: IO ()
+> main = defaultMain
diff --git a/Text/HTML/SanitizeXSS.hs b/Text/HTML/SanitizeXSS.hs
new file mode 100644
--- /dev/null
+++ b/Text/HTML/SanitizeXSS.hs
@@ -0,0 +1,88 @@
+module Text.HTML.SanitizeXSS (sanitizeXSS) where
+
+import Text.HTML.TagSoup
+
+import Data.Set (Set(), member, fromList)
+import Data.Char ( toLower )
+
+import Network.URI ( parseURIReference, URI (..),
+                     isAllowedInURI, escapeURIString, uriScheme )
+import Codec.Binary.UTF8.String ( encodeString )
+
+sanitizeXSS :: String -> String
+sanitizeXSS = renderTagsOptions renderOptions {
+    optMinimize = \x -> x `elem` ["br","img"] -- <img><img> converts to <img />, <a/> converts to <a></a>
+  } .  safeTags . parseTags
+  where
+    safeTags :: [Tag String] -> [Tag String]
+    safeTags [] = []
+    safeTags (t@(TagClose name):tags) | safeTagName name = t:(safeTags tags)
+                                      | otherwise = safeTags tags
+    safeTags (TagOpen name attributes:tags)
+      | safeTagName name = TagOpen name (filter safeAttribute attributes) : safeTags tags
+      | otherwise = safeTags tags
+    safeTags (t:tags) = t:safeTags tags
+
+safeTagName :: String -> Bool
+safeTagName tagname = tagname `member` sanitaryTags
+
+safeAttribute :: (String, String) -> Bool
+safeAttribute (name, value) = name `member` sanitaryAttributes &&
+  (name `notElem` ["href","src"] || sanitaryURI value)
+         
+
+-- | Returns @True@ if the specified URI is not a potential security risk.
+sanitaryURI :: String -> Bool
+sanitaryURI u =
+  case parseURIReference (escapeURI u) of
+     Just p  -> (map toLower $ uriScheme p) `member` safeURISchemes
+     Nothing -> False
+
+
+-- | Escape unicode characters in a URI.  Characters that are
+-- already valid in a URI, including % and ?, are left alone.
+escapeURI :: String -> String
+escapeURI = escapeURIString isAllowedInURI . encodeString
+
+
+safeURISchemes :: Set String
+safeURISchemes = fromList [ "", "http:", "https:", "ftp:", "mailto:", "file:",
+             "telnet:", "gopher:", "aaa:", "aaas:", "acap:", "cap:", "cid:",
+             "crid:", "dav:", "dict:", "dns:", "fax:", "go:", "h323:", "im:",
+             "imap:", "ldap:", "mid:", "news:", "nfs:", "nntp:", "pop:",
+             "pres:", "sip:", "sips:", "snmp:", "tel:", "urn:", "wais:",
+             "xmpp:", "z39.50r:", "z39.50s:", "aim:", "callto:", "cvs:",
+             "ed2k:", "feed:", "fish:", "gg:", "irc:", "ircs:", "lastfm:",
+             "ldaps:", "magnet:", "mms:", "msnim:", "notes:", "rsync:",
+             "secondlife:", "skype:", "ssh:", "sftp:", "smb:", "sms:",
+             "snews:", "webcal:", "ymsgr:"]
+
+sanitaryTags :: Set String
+sanitaryTags = fromList ["a", "abbr", "acronym", "address", "area", "b", "big",
+                "blockquote", "br", "button", "caption", "center",
+                "cite", "code", "col", "colgroup", "dd", "del", "dfn",
+                "dir", "div", "dl", "dt", "em", "fieldset", "font",
+                "form", "h1", "h2", "h3", "h4", "h5", "h6", "hr",
+                "i", "img", "input", "ins", "kbd", "label", "legend",
+                "li", "map", "menu", "ol", "optgroup", "option", "p",
+                "pre", "q", "s", "samp", "select", "small", "span",
+                "strike", "strong", "sub", "sup", "table", "tbody",
+                "td", "textarea", "tfoot", "th", "thead", "tr", "tt",
+                "u", "ul", "var"]
+
+sanitaryAttributes :: Set String
+sanitaryAttributes = fromList ["abbr", "accept", "accept-charset",
+                      "accesskey", "action", "align", "alt", "axis",
+                      "border", "cellpadding", "cellspacing", "char",
+                      "charoff", "charset", "checked", "cite", "class",
+                      "clear", "cols", "colspan", "color", "compact",
+                      "coords", "datetime", "dir", "disabled",
+                      "enctype", "for", "frame", "headers", "height",
+                      "href", "hreflang", "hspace", "id", "ismap",
+                      "label", "lang", "longdesc", "maxlength", "media",
+                      "method", "multiple", "name", "nohref", "noshade",
+                      "nowrap", "prompt", "readonly", "rel", "rev",
+                      "rows", "rowspan", "rules", "scope", "selected",
+                      "shape", "size", "span", "src", "start",
+                      "summary", "tabindex", "target", "title", "type",
+                      "usemap", "valign", "value", "vspace", "width"]
diff --git a/xss-sanitize.cabal b/xss-sanitize.cabal
new file mode 100644
--- /dev/null
+++ b/xss-sanitize.cabal
@@ -0,0 +1,25 @@
+name:            xss-sanitize
+version:         0.1
+license:         BSD3
+license-file:    LICENSE
+author:          Greg Weber <greg@gregweber.info>
+maintainer:      Greg Weber <greg@gregweber.info>
+synopsis:        sanitize untrusted HTML to prevent XSS attacks
+description:     sanitize untrusted HTML to prevent XSS attacks with Text.HTML.SanitizeXSS.sanitizeXSS. see README.md for more details
+
+category:        Text.HTML
+stability:       Stable
+cabal-version:   >= 1.6 
+build-type:      Simple
+homepage:        http://github.com/gregwebs/haskell-xss-sanitize
+
+library
+    build-depends:  base == 4.*, containers,
+                    tagsoup >= 0.11, utf8-string >= 0.3, network >= 2
+
+    exposed-modules: Text.HTML.SanitizeXSS
+    ghc-options:     -Wall
+
+source-repository head
+  type:     git 
+  location: http://github.com/gregwebs/haskell-xss-sanitize.git
