Why doesn't my text filter strip out the tags I don't want?
Okay, I've found my own answer. The problem was with my understanding of how text filters work. Text is saved into the database with no changes, and filters are only applied when the text is output.

When using Entity Metadata Wrappers, as I am, when you get $wrapper->body->value(), it gives you an array with several elements. One is the "value", which is the raw, unfiltered text. Another is the "safe_value", which has had filters applied.

So in my case I needed to use the "safe_value" when displaying the text. Makes sense once you understand it!