PHP, Zend Framework and Other Crazy Stuff
PHP Security
Out With The Old, In With The New: Original MySQL Extension Heading For Retirement?
Jul 16th
When we use the term PHP, we are often silently associating it with the abbreviation LAMP (that’s Linux, Apache, MySQL and PHP just in case you don’t recall). MySQL has been our bread and butter in PHP for over a decade; an old friend, accomplice and partner in crime. This was made possible with the MySQL extension. Indeed, you can scarcely find a basic nuts and bolts PHP tutorial that doesn’t use MySQL. Which is probably why it’s a good idea to give it a huge going away bash (and make sure it finds the exit afterwards and catches a cab to oblivion!). We’ve since seen replacements like the MySQL Improved extension (mysqli) and PHP Data Objects (PDO). These are simply better from the additional features each adds to their integration in higher level libraries such as Doctrine.
But, as with any basic change to a successful formula, there was bound to be some controversy at the mere suggestion of deprecating our old friend (even if preceded by an extended period of educating users on the well established replacements). Manuel Limos and Lucas Darnell have both written blog posts indicating what a bad idea this could be. Their issues are understandable. Once the E_DEPRECATION notices start flying applications that have existed for years (and years) will appear to implode leaving behind a long line of irritated people who may need to hire a PHP programmer to fix stuff. This obviously imposes a cash cost across thousands (probably an underestimation
) of businesses. This may lead to hosting services deferring adoption of the PHP version carrying the deprecation by months if not years. Lucas also raised an interesting point that with so much literature, including books, carrying example after example of (often insecure in my opinion) MySQL extension use, user adoption and education may suffer a great deal.
In a riposte to Manual Lemos, Gregg Thomason perhaps illustrates best why even the feared disadvantages may be worth the cost. MySQL is a historical relic from a past PHP is trying to leave behind. It’s old, doesn’t do a lot to support security and it needs to go. I agree. Gregg says “…this is a forward-thinking business and our job is to invent the future.” Let’s go invent and improve that future – if nothing else it might make Anonymous’ job finding SQL injections at every company they squint at a little harder
.
PHP is not a weirdo stagnant programming language used by amateurs who don’t have sufficient brain cells to learn Java, Ruby or Python. That’s the common misconception based largely on two obvious factors: PHP is so amazingly popular and easy to learn that any innocently ignorant person with half a brain cell can write a fabulously insecure application (the examples just keep coming and coming) and, secondly, PHP is a bit on the ugly side and not a “true object oriented language” because it uses functions instead of methods. PHP is actually used by hardcore professionals who build great secure applications and that community has left the original MySQL extension by the wayside in favour of object oriented solutions where MySQL related functions are buried deep behind a wall of classes in their preferred database interaction solution, such as PDO or Doctrine. It’s about time we brought everyone else up to speed with that reality.
While “deprecation” may attract all the attention, let’s remember that pushing the alternatives by any possible means is a great idea. Philip Olson’s proposal on how to encourage users to move away from the original MySQL extension has a lot of merit and is well worth persuing. We need to let go of the past eventually to keep PHP moving into the future.
How Would You Engineer A PEAR2/Pyrus Distribution Architecture?
Jun 20th
I was recently accused on the Zend Framework Contributors mailing list of having “strong feelings” towards Pyrus (i.e. the PEAR Group’s Installer/Packager for PEAR2) and not in a positive way. It’s a fair description. PEAR is, putting it lightly, a very old architecture which makes it very resistant to change. With the idea of PEAR2 and Pyrus, I had hoped to see a renewal – the advancement of a PEAR architecture for the 21st Century. Instead, and this is just my opinion, PEAR2/Pyrus were a relatively simple iteration on a very old theme.
A Ranting We Shall Go
Now, I may be biased since I gave up on PEAR becoming PHP’s core distribution mechanism after I found myself using alternative strategies for hosting and deployment. This is not to say PEAR is not useful for everyone. It is – just not in my specific case when developing/testing/deploying applications. It still remains a good distribution means regardless by virtue of its ubiquitous installation with PHP.
I surprised even myself, however, with my vehement outcry over the idea of adopting Pyrus as Zend Framework 2′s package distribution method, lambasting both it and the PEAR concept of distribution in equal measures while piling up questions on Pyrus’ status (currently released in alpha) and suitability in the near term. That thread showed a fairly divided sentiment. Once I jokingly threatened to mow down my zombified colleagues with a minigun, I figured it was time to go forth and rant (miniguns are too expensive for these recessionary times).
If the PEAR ecosystem has a failing, it is one of staggered evolution. Over time it has picked up additional features tacked on top of a base model. The classic example is the use of Channels (to support multiple repositories) that has more recently prompted calls for the use of a Channel Aggregator to avoid the use cost in locally managing a channel registry or even hosting a Channel. This is the way of many PEAR features. They each do something incredibly useful but do it in a way that has many developers looking for a better approach – usually to discover the better approach requires breaking compatibility.
My vehemence in the afore mentioned mailing list was down to a simple case of disappointment. We all deal with PEAR because we have it, we know it, and have done so for years. Seeing PEAR2 and Pyrus take the incremental improvement route without apparently doing anything to change the core experience seemed…pointless. It improved a lot of what PEAR already did without actually doing very much different. All the same advantages, disadvantages, features and lack thereof were present and accounted for with a handful of nice headline changes (e.g. we now have package signing capability). What exactly was the purpose of rewriting the entire toolchain if not to seize the opportunity to answer the accusations of those who doubt PEAR is even relevant these days – by making it the single most relevant development in PHP today?
One Possible Path Forward
Since this is a brain dump post, as much to gather my own throughts in one place as anything else, feel free to call me bat shit crazy. There are days even I think that. Below I’ve raised what I perceive as problems in the PEAR/Pyrus system, obviously from a personal perspective, and possible solutions under the categories of Packaging, Distribution, Installation and Usage. I’ve tried to avoid getting into technical details – broad strokes will suffice for now. For your sanity, only the Packaging and Distribution areas are presented today. I will add a similar post for Installation and Usage later in the week. First one to mention “TL:DR” gets a minigun round to the head (will have to make do with throwing it at you until I can scrape more cash together for the hardware). To avoid any confusion, I use the terms PEAR and Pyrus to refer to the entire workflow from package generation to end usage for each respectively.
Packaging
The packaging of source code for PEAR is performed using the PEAR/Pyrus Installer coupled with a Package Definition (i.e. package.xml) to create a distributable archive file. Pyrus utilises a slightly more friendly Package Definition by also allowing for some elements of the definition to be defined in files other than package.xml (e.g. for setting up a changelog file or version numbers). The basic goal of this Package Definition is to have at least one XML file which tells PEAR/Pyrus which files to package, while role a file has (code/docs/tests), where each file goes in a relative filesystem, optionally the file’s MD5 hash, and a set of metadata like the package name, changelog, version, dependencies, etc. Using Pyrus offers the additional feature of being able to cryptographically sign packages, use a larger number of archive formats including PHAR, and bundle certain package dependencies internally.
Problems:
The main problem with the current Package Definition is that it often must be generated by a separate tool since it’s XML (it’s that thing everyone used before discovering YAML/JSON), and must explicitly list every file and piece of data within that format (with the exception of Pyrus which allows specifically formatted files to carry version and changelog information among other nuggets) optionally with each file’s digest hash. Even the Pyrus improvements still require specific files using specific formatted text and/or file names. Using XML just ends up imposing extra work to maintain package details unless you are lucky enough to have a small stable enough package.xml that it can be manually maintained rather then persistently needing generation. A minor aesthetic detail is that XML is harder to read.
Secondly, packages are therefore bound to their archiving restraints. Since package.xml generation is tied to a secondary process, installing from source code may not be feasible whether performed on a local git clone or similarly automated from a remote source where the remote package.xml may well be out of sync with the actual source code or where it may not even exist.
Possible Solutions:
The one solution that keeps occuring to me is to simply make a Package Definition programmable, i.e. a small consumable low-maintenance PHP script. Using native PHP, one can create ether a generic array, or a newfangled closure, which can be executed through PHP to populate all the necessary data for a Package Definition for consumption by a package installer.
Since I’ve dabbled a bit, here’s what such a Package Definition could look like:
<?php $package = function ($s) { $s->name = 'Overlord'; $s->authors = 'Padraic Brady, Sauron[sauron@mordor.me]'; $s->version = '0.0.1-dev'; $s->api_version = '0.0.1-dev'; $s->summary = 'Monitoring library for Hobbit Detector 1.0'; $s->description = file_get_contents(__DIR__ . '/description.txt'); $s->homepage = 'http://en.wikipedia.org/wiki/Sauron'; $s->changelog = file_get_contents(__DIR__ . '/changelog.txt'); $s->files['php'][] = 'library/**/*.php'; $s->files['tests'][] = 'tests/**/*.*'; $s->files['ignore'][] = '*.project'; $s->files['bin'][] = 'scripts/overlord.bat'; $s->include_path = 'Overlord/Monitor/'; $s->dependencies[] = 'PHP[>=5.3.1]'; $s->dependencies[] = 'Pear[>=1.6.5]'; $s->dependencies[] = 'MutateMe[0.5.0]'; $s->dependencies[] = 'ext/runkit'; $s->optional_dependencies[] = 'ext/eyeofsauron'; $s->license = 'New BSD'; };
I’ll assume PHP 5.4 will have some sort of short array notation to cut down the array size. Well, let’s hope so
. Would be nice to reduce the line count more. Yes, I did indeed borrow the idea from elsewhere
.
This has a few advantages. No XML to maintain. No need to keep an XML Package Definition synced up for every file change in a VCS. No need for secondary XML generation tools or build tool plugins. Supports downloading files from remote herarchical sources and not just archives (including any VCS source). Developers are already used to versioning build scripts from tools like Phing (just not the end products which are usually ignored whereas package.xml is not). Being plain old PHP, it can be just as complex or as minimal as you want and anyone with basic PHP knowledge can write one.
One can still generate signable archive files using this approach – the point is to increase the kind of installation sources that can be used rather than replace existing ones. In place of signable packages, for those requiring the security, other package files could be limited to download over HTTPS. For example, Github offers git read-only access via HTTPS for all repositories as standard.
Distribution
In order to distribute source code using PEAR/Pyrus, you need to make use of either a PEAR Channel or a standalone archive download (i.e. a downloadable tarball). A Channel is basically a whole bunch of XML files served up for access as a REST API. Using a Channel, you can upload packages to the Channel host, update the XML files, and publicise your Channel URI so users can discover your Channel and install your packages.
Problems:
PEAR (PHP Extension and Application Repository) was originally founded to serve as a central package distribution channel. For various real and imagined reasons, the concept of a central repository did not succeed in PHP and instead developers insisted on using alternative means. This was aggravated even further by the arrival of frameworks like Zend Framework offering discrete components not originally served over a PEAR Channel at all. PEAR Channels were introduced to allow anyone host their own distinct PEAR Channel as one of those means.
The PEAR Installer has only ever shipped with the main PEAR Channels pre-registered. All other Channels needed to be manually located before use – usually by referring to the packaged library’s documentation. Since all Channels are independent entities, there is no global lookup point for querying package details, dependencies and availability. There is also no scope for true package name uniqueness (technically this is accomplished by requiring all packages (except core-PEAR ones) are prefixed with a Channel alias term, e.g. mychannel/MyPackage).
The generation of the REST API, which was the backbone of a Channel, was also complex (the release of Pirum by Fabien Potencier has gone a long way towards simplifying this). Obviously, Channels are also tied to the concept of archive packages and cannot operate directly with a VCS like git. There is a workaround possible for Github using Github Pages to host the REST API.
As alluded to, the REST API is itself a complex graph of XML files that requires a generation tool to manage initial setup and package updates.
Possible Solutions:
The best concept to gain early traction was that of a Channel Aggregator expressed by Stuart Herbert. Sadly, I haven’t seen much more action on that front. In commenting on that idea, I considered it a move towards a decentralised distributed Channel mechanism (mouthful of gibberish, I know!). Here’s a couple of thoughts on how this could work:
The players would include a Package Authority, a Channel Aggregator (any number of them), and Channels (optional).
The Package Authority would be a centralised location basically for reserving package names and ensuring there is a point of reference and authority to prevent package name duplication and to manage ownership of such. It’s possible this could also be developed with additional purposes but let’s keep it simple. This would help, primarily, in removing the need for Channel prefixes on package names and preventing package name confusion. For security reasons, the Package Authority would associate a package name to a specific URI representing a download source (e.g. a PEAR Channel or Git URI)
Channel Aggregators are the more complex beasts. They may be utilised by Channel operators to distribute Package metadata to end-users on demand. The Aggregator would track available packages at source, their basic details, their available versions, and information on the location of host Channels, version control systems, and Package URIs and so forth. In effect, the Aggregator might well replace Channels for many purposes – and potentially eliminate one more source of work in distributing source code using PEAR/Pyrus.
The ideal scenario here is that any PEAR/Pyrus Installer would pre-register a couple of well-maintained Aggregators saving the users and package distributors the annoyance of dealing with Channels altogether. Hence, we’re back to a core Channel of sorts but with control of package/source hosting decentralised to individual developers. Again, Aggregators could easily repurpose themselves as package hosters if they wish (such as Pearfarm are doing) though this would be entirely optional.
Channels, as suggested, could well be optional. Use an Aggregator instead and register either a package URI, git repository, or anything else so long as it lets you download the package files (and the PHP programmable Package Definition
). Painless hosting? Maybe.
I will point out this would require at least one authentication in the system. You’d need a Package Authority account to allow for reserving a package name and perhaps transferring it between maintainers. The Aggregator may operate without authentication since it acts much like any aggregator based on your source data (and one would hope a few simple crosschecks with the Package Authority to ensure it’s not unwittingly aggregating false data from the hackers
. Package/source hosters could ping the Aggregator as a hint to update its date in a more timely manner.
I won’t touch the issue of who gets to reserve the package name “DB”. The Package Authority may need to enforce specific rules against overly generic names on a common sense basis.
I think that’s enough for a Monday read (you’ll all need enough brain capacity to finish out the week!). Feedback is, as usual, welcome. If anyone has a pre-existing solution or one in planning along these or similar lines, drop a comment!
CodeIgniter 2.0.2: Cross-Site Scripting (XSS) Fixes And Recommendations
May 10th
As many of my readers know, I have a keen dislike for regular expression based HTML sanitisation. Regular expressions simply do not understand HTML’s nested nature and the numerous possible HTML/CSS standards it must abide by. The result is that far too many developers try to program this understanding (and unfortunately their lack of comprehensive understanding) into home grown sanitisers using as little code and tests as possible. It’s a horrendous and reprehensible practice that has created a large field of so-called sanitisers and XSS cleaners which are riddled with obvious vulnerabilities despite all their sincere and utterly false claims to the contrary. The perception of safety they create is almost always a fantasy. As I’ve said before, this serves only one purpose – to lend support to claims that PHP is insecure. And why disagree given PHP’s prominence on the internet and this continuing refusal by developers to just do the right thing and use a secure solution that really does work?
Since I’ve completed my research into a broad set of these, for now, I’ll close with a final example given its widespread usage, confusing documentation and lack of a clear disclosure to date of security vulnerabilities.
On April 7, EllisLab released CodeIgniter 2.0.2 as a security maintenance release prompted by a report I sent to EllisLab shortly before St. Paddy’s Day (around mid-March). That report indicated the expected response and my own disclosure policy. This blog post is being published in accordance with those. The disclosure to date of the vulnerabilities afflicting previous CodeIgniter versions is mentioned only in the CodeIgniter 2.0.2 news release (from April 7) as follows:
An update to both CodeIgniter Reactor and CodeIgniter Core (v 2.0.1) was released today. This is a security maintenance release and is a recommended update for all sites. The security fix patches a small vulnerability in the cross site scripting filter.
EllisLab’s news release for CodeIgniter 2.0.2 makes mention of “a small vulnerability”. This small vulnerability is mentioned no where else (not even the actual changelog for 2.0.2). In reality, I reported seven distinct vulnerabilities across two classes. These vulnerabilities might allow an attacker to inject arbitrary HTML, CSS or Javascript, i.e. Cross-Site Scripting (XSS) into an application’s output. It would be nice if, in the future, EllisLab aim for more accuracy in their news releases and disclosed both the number and nature of the security vulnerabilities fixed in their release changelogs.
Users of CodeIgniter 2.0.x and 1.7.x are strongly urged to upgrade to CodeIgniter 2.0.2 (or later) as soon as possible to avail of these critical security fixes.
In addition, users are urged to follow some basic steps when writing or updating CodeIgniter applications:
- Escape ALL data being injected into views using PHP’s htmlspecialchars() function, remembering to pass the character encoding being used as the third parameter. A helper function may be useful to keep the typing to a minimum.
- Use HTMLPurifier when you need to sanitise HTML data or user input such as HTML comments, HTML emails, or RSS/Atom content (basically any HTML you do not explicitly generate yourself!).
- Ensure that all HTML pages are served with a valid Content-Type HTTP header and/or a meta tag equivalent which also declares the charset for that page. Note that HTML5 offers a separate charset element for this purpose. This helps prevent character encoding based XSS attacks by informing the browser of the correct character encoding to use.
- Ensure that all views/templates distributed by third parties are likewise reviewed to ensure they utilise proper escaping and XSS sanitisation.
CodeIgniter is one of the most prominent “micro frameworks”, web application frameworks that prosper by offering their users unparalleled simplicity. It is unusual as a framework in that it does not make any reference to standard escaping mechanisms for views/templates such as the PHP htmlspecialchars() function anywhere in its source code, examples or documentation. This may create the unfortunate impression that users should instead filter input using an XSS filter function in the CI_Security class and do nothing on output. Users taking this approach may be particularly at risk from these security vulnerabilities.
My recommendation to the CodeIgniter developers, as documented in my original report, is to deprecate and remove the CI_Security class’ XSS filter. Responsible vendors should never persist in distributing and advocating the use of insecure software. I also urge them to revise their documentation to ensure that best security practice is noted in the area of writing views/templates and offer a shortcut function to an escaping mechanism for HTML output to standardise and ease its use by members.
Private vs Protected Methods: The Debate That Never Ends
Mar 28th
As a new generation of PHP web application frameworks start establishing beach heads in preparation for an all out war for mindshare, I’ve been contemplating some of the key changes we’re seeing emerge that may gain traction over time. Today, I just thought to share my thoughts about private methods vs protected methods. Something that has impacted on Doctrine 2 and Symfony 2. And every other piece of PHP code since PHP 4 came along.
Doctrine and Symfony have adopted a practice whereby all methods are flagged as private unless there are compelling reasons to the contrary (i.e. they are necessarily part of a public API, or form part of an abstract implementation). There is an allowance for appealing that a method be switched from private to protected on a case by case basis.
I’m not a big fan of private methods. I’m in a state of persistent internal conflict over them, in fact. At this point in time, I am in favour of protected methods over private methods.
On one hand, there’s the traditional idea that private methods must never be used outside the current class to prevent interaction with and dependencies on highly unstable units of behaviour. This is a common need arising out of refactoring where non-public methods may vanish, move classes, get renamed or find themselves saddled with different behaviour. These non-public methods are inherently unstable. There is no getting away from that. While this is perfectly normal during development, it can become an irritating concept downstream if you are depending on such implementation details in your own work.
On the other hand, non-public methods contain the meat of any class – the implementation specifics. Using Inheritance, you can extend classes containing this implementation code at very little cost if they are carried by protected methods. This usually tends to make life easier, promotes source code reuse, allows for bug and security fixes ahead of schedule (assuming there it’s not one of those lost bugs that will never be fixed), dependence on existing implementation facets, etc. This all does, however, carry the risk of creating a lot of brittle code that may break at the next update due to the effects of refactoring.
These two strands of thought have been in combat for a long time in many programming languages. It’s no different in PHP. Many developers are likely under the delusion that protected methods constitute a protected API, i.e. that there is a rule that protected methods must retain backwards compatibility across updates. There is no such rule. There is, however, a form of peer pressure to conform to these delusions. Ask any set of developers about backwards compatibility and there’s probably a fair chance that the majority will insist that it applies to protected methods. Unfortunately, this is in direct violation of the principles behind refactoring. In other words, it’s a delusional belief with no basis in software engineering. Since delusional is a strong term, I’ll refer to it as the misguided status quo. There – that’s surely less offensive.
In my view, this is where PHP developers seem to like throwing themselves off a cliff. Rather than perform refactoring, they would rather preserve backwards compatibility on methods that are not even unit tested. All for a group of people down the line who are unwilling to accept that they took a risk in depending on them. If that sounds a wee bit bitter, it is. I’ve seen people do some crazy weird shit to avoid refactoring or other key changes that would make my life so much easier. Some circumstances push me into creating ridiculous brittle workarounds instead.
So we not only have two varying views, we also have two extreme solutions: mark everything private or actively discourage refactoring even to the extent of inventing stupid mind boggling excuses according to some unwritten rule book maintained by the anonymous hivemind (that’s the PHP one, not the shit crazy hacker one). Me? I’m a fan of the middle path: use protected methods unless otherwise required by design, refactor at will and tell anyone who complains about downstream brittleness that I don’t unit test protected methods and that it is fundamentally necessary to change them at times. Most developers are intelligent enough to understand that they took a risk. We can both have our cake and eat it without undue hostility by being a little more upfront.
So why did I finally decide that private methods are not worth enforcing? Like many similar beliefs, it crept up on me over time. Back in the day, when I was programming in Java as a young kid with no sense of independent thought, I would have taken my blind puritanical righteousness to extremes and pounced on anyone who questioned the party line about private methods. These days, I program in PHP, Ruby, and Python. I try to avoid Perl. None of these have enforceable private methods. Perl doesn’t have private visibility at all without hacks. Ruby and Python have easily bypassed implementations (i.e. they are more of a low fence with a “No Trespassing” sign you can hop over as needed – at the risk of being prosecuted and fined). PHP stands out due to that evil conniving Java influence that is overquoted. Too much in PHP is already “influenced” or “inspired” by a language I dropped like a hot coal over a decade ago. I can’t wait until we get over Java as the PHP deity of choice…
Private methods represent more than a “No Trespassing” sign in PHP/Java. In PHP, they are enforceable in theory, i.e. you just cannot call or invoke them. End of story. In reality, this is hogwash. As our colleagues in Ruby, Python, Perl, and all the other programming languages without private visibility enforcement already know – you cannot impose a practice. There will always be the vocal group that tell you to fuck off while they blissfully inherit classes and reuse private methods anyway. It’s easy to do. All you need is a text editor and the keyboard keys CTRL, C and V. Or Reflection. Or sed and grep. Or git. Private visibility enforcement is an illusion (or delusion?).
Once you reach into the dark corners of your mind and realise that private methods and properties are just another pointless easy-to-overcome obstacle, the real problem remains entirely unchanged and unaddressed. How to manage the expectations of downstream users who can and will extend and reuse your code in ways you never expected?
All the band aids in the world won’t answer that, and my response would be to be clearly honest and open. Educate downstream users about your need for refactoring and then make it clear that they proceed at their own damn risk if relying on non-public methods! Then I, you, and the rest of the PHP community can hack, inherit, and gleefully ignore best practices when it suits us without worrying about private method blockades. Not that that would stop us anyway…
Anyway, there’s my train of thought for today. Private methods are nice in theory but unenforceable in practice. If they really were enforceable, those of us who keep creating excuses to fiddle around with core library and framework code might blow a gasket or three out of frustration if the Nanny State mentality really took hold.
Open Letter to Gareth Heyes: Regex HTML Sanitisation Doesn’t Work
Mar 18th
Dear Gareth Heyes,
I thank you for your response that claims Regex HTML Sanitisation can work.
However, I should clarify that my article, Regex HTML Sanitisation: Off With Its Head!, was written in the context of using Perl regular expressions in PHP to both parse and filter HTML. Your challenge to test HTMLReg was unusual since HTMLReg is written in Javascript, operates as a client side library, and utilises the browser DOM to bypass HTML parsing with regular expressions.
As such, HTMLReg and your article title falls outside the context of my original article. I do, however, applaud the concept of using the browser DOM. While I cannot comment on the efficacy of client side filtering for cross-site scripting (XSS), the use of a DOM is a reliable strategy to bypass parsing problems. A similar approach accounts for the success of HTMLPurifier. Obviously, I do not begrudge some minimal use of regular expressions on pre-parsed normalised input.
This did, however, prompt me to ponder whether such an inapplicable challenge appearing on Planet-PHP undermines my argument anyway by its mere existence and blunt title in a world populated by A.D.D. sufferers. I believed it might and so I found myself determined to crack your Javascript library over a cup of coffee and a biscuit.
The result of this quick examination cannot be publicly reported here as this would be poor reporting practice. Therefore, I will report the resulting security vulnerability by email. You now have six weeks from today’s date in which to release a fixed version of HTMLReg and publicly disclose this vulnerability. I trust you will ensure that all similar or related potential vulnerabilities are also fixed. It would also, optionally, be interesting to see a blog post on the effectiveness of a client side Javascript filter.










