diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,7 @@
 Changelog for Hexml
 
+0.2
+    #6, remove buffer overruns
+    #5, add lower bounds
 0.1
     Initial version
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -8,4 +8,4 @@
 
 The name "hexml" is a combination of "Hex" (a curse) and "XML". The "X" should not be capitalised because the parser is more curse and less XML.
 
-Hexml may be suitable if you want to quickly parse XML, from known sources, and a full XML parser has been shown to be a bottleneck. As an alternative to hexml, which supports things like entities but is still pretty fast, see Pugixml.
+Hexml may be suitable if you want to quickly parse XML, from known sources, and a full XML parser has been shown to be a bottleneck. As an alternative to hexml, which supports things like entities but is still pretty fast, see [Pugixml](http://pugixml.org/) (with a [Haskell binding](https://hackage.haskell.org/package/pugixml)).
diff --git a/cbits/hexml.c b/cbits/hexml.c
--- a/cbits/hexml.c
+++ b/cbits/hexml.c
@@ -10,18 +10,18 @@
 /////////////////////////////////////////////////////////////////////
 // TYPES
 
-int static inline end(str s) { return s.start + s.length; }
+static inline int end(str s) { return s.start + s.length; }
 
-str static inline start_length(int32_t start, int32_t length)
+static inline str start_length(int32_t start, int32_t length)
 {
-    if (start < 0 || length < 0) assert(0);
+    assert(start >= 0 && length >= 0);
     str res;
     res.start = start;
     res.length = length;
     return res;
 }
 
-str static inline start_end(int32_t start, int32_t end)
+static inline str start_end(int32_t start, int32_t end)
 {
     return start_length(start, end - start);
 }
@@ -51,11 +51,11 @@
 
 struct document
 {
-    char* body; // pointer to initial argument, not owned by us
+    const char* body; // pointer to initial argument, not owned by us
 
     // things only used while parsing
-    char* cursor; // pointer to where we are in body
-    char* end; // pointer to one past the last char
+    const char* cursor; // pointer to where we are in body
+    const char* end; // pointer to one past the last char
     // if cursor is > end we have gone past the end
 
     char* error_message;
@@ -63,8 +63,8 @@
     attr_buffer attrs;
 };
 
-int static inline doc_length(document* d) { return (int) (d->end - d->body); }
-int static inline doc_position(document* d) { return (int) (d->cursor - d->body); }
+static inline int doc_length(const document* d) { return (int) (d->end - d->body); }
+static inline int doc_position(const document* d) { return (int) (d->cursor - d->body); }
 
 
 /////////////////////////////////////////////////////////////////////
@@ -72,49 +72,45 @@
 
 typedef struct
 {
-    document* d;
+    const document* d;
     char* buffer;
     int length;
     int cursor;
 } render;
 
-void static inline render_char(render* r, char c)
+static inline void render_char(render* r, char c)
 {
     if (r->cursor < r->length)
         r->buffer[r->cursor] = c;
     r->cursor++;
 }
 
-void static inline bound(char* msg, int index, int mn, int mx)
+static inline void bound(int idx, int mn, int mx)
 {
-    if (index < mn || index > mx)
-    {
-        //printf("Bounds checking failed %s, got %i, which should be in %i:%i\n", msg, index, mn, mx);
-        assert(0);
-    }
+    assert(idx >= mn && idx <= mx);
 }
 
-void static inline bound_str(char* msg, str s, int mn, int mx)
+static inline void bound_str(str s, int mn, int mx)
 {
-    if (s.length < 0) assert(0);
-    bound(msg, s.start, mn, mx);
-    bound(msg, end(s), mn, mx);
+    assert(s.length >= 0);
+    bound(s.start, mn, mx);
+    bound(end(s), mn, mx);
 }
 
 void render_str(render* r, str s)
 {
-    bound_str("render_str", s, 0, doc_length(r->d));
+    bound_str(s, 0, doc_length(r->d));
     for (int i = 0; i < s.length; i++)
         render_char(r, r->d->body[s.start + i]);
 }
 
-void render_tag(render* r, node* n);
+void render_tag(render* r, const node* n);
 
-void render_content(render* r, node* n)
+void render_content(render* r, const node* n)
 {
-    bound_str("render_conent inner", n->inner, 0, doc_length(r->d));
-    bound_str("render_conent nodes", n->nodes, 0, r->d->nodes.used_front);
-    bound_str("render_conent attrs", n->attrs, 0, r->d->attrs.used);
+    bound_str(n->inner, 0, doc_length(r->d));
+    bound_str(n->nodes, 0, r->d->nodes.used_front);
+    bound_str(n->attrs, 0, r->d->attrs.used);
 
     int done = n->inner.start;
     for (int i = 0; i < n->nodes.length; i++)
@@ -127,7 +123,7 @@
     render_str(r, start_end(done, end(n->inner)));
 }
 
-void render_tag(render* r, node* n)
+void render_tag(render* r, const node* n)
 {
     render_char(r, '<');
     render_str(r, n->name);
@@ -149,7 +145,7 @@
     render_char(r, '>');
 }
 
-int node_render(document* d, node* n, char* buffer, int length)
+int node_render(const document* d, const node* n, char* buffer, int length)
 {
     render r;
     r.d = d;
@@ -168,8 +164,8 @@
 /////////////////////////////////////////////////////////////////////
 // NOT THE PARSER
 
-char* document_error(document* d){return d->error_message;}
-node* document_node(document* d){return &d->nodes.nodes[0];}
+char* document_error(const document* d){return d->error_message;}
+node* document_node(const document* d){return &d->nodes.nodes[0];}
 
 void document_free(document* d)
 {
@@ -179,20 +175,20 @@
     free(d);
 }
 
-node* node_children(document* d, node* n, int* res)
+node* node_children(const document* d, const node* n, int* res)
 {
     *res = n->nodes.length;
     return &d->nodes.nodes[n->nodes.start];
 }
 
-attr* node_attributes(document* d, node* n, int* res)
+attr* node_attributes(const document* d, const node* n, int* res)
 {
     *res = n->attrs.length;
     return &d->attrs.attrs[n->attrs.start];
 }
 
 
-attr* node_attributeBy(document* d, node* n, char* s, int slen)
+attr* node_attributeBy(const document* d, const node* n, const char* s, int slen)
 {
     if (slen == -1) slen = (int) strlen(s);
     const int limit = end(n->attrs);
@@ -206,7 +202,7 @@
 }
 
 // Search for given strings within a node
-node* node_childBy(document* d, node* parent, node* prev, char* s, int slen)
+node* node_childBy(const document* d, const node* parent, const node* prev, const char* s, int slen)
 {
     if (slen == -1) slen = (int) strlen(s);
     int i = prev == NULL ? parent->nodes.start : (int) (prev + 1 - d->nodes.nodes);
@@ -244,22 +240,22 @@
     done = 1;
 }
 
-bool static inline is(char c, char tag) { return parse_table[c] & tag; }
-bool static inline is_name1(char c) { return is(c, tag_name1); }
-bool static inline is_name(char c) { return is(c, tag_name); }
-bool static inline is_space(char c) { return is(c, tag_space); }
+static inline bool is(char c, char tag) { return parse_table[c] & tag; }
+static inline bool is_name1(char c) { return is(c, tag_name1); }
+static inline bool is_name(char c) { return is(c, tag_name); }
+static inline bool is_space(char c) { return is(c, tag_space); }
 
 
 /////////////////////////////////////////////////////////////////////
 // PARSER COMBINATORS
 
-char static inline peekAt(document* d, int i) { return d->cursor[i]; }
-void static inline skip(document* d, int i) { d->cursor += i; }
-char static inline peek(document* d) { return peekAt(d, 0); }
-char static inline get(document* d) { char c = peek(d); skip(d, 1); return c; }
+static inline char peekAt(const document* d, int i) { return d->cursor[i]; }
+static inline char peek(const document* d) { return peekAt(d, 0); }
+static inline void skip(document* d, int i) { d->cursor += i; }
+static inline char get(document* d) { char c = peek(d); skip(d, 1); return c; }
 
 // Remove whitespace characters from the cursor while they are still whitespace
-void static inline trim(document* d)
+static inline void trim(document* d)
 {
     while (is_space(peek(d)))
         skip(d, 1);
@@ -285,7 +281,7 @@
 /////////////////////////////////////////////////////////////////////
 // PARSING CODE
 
-void static inline node_alloc(node_buffer* b, int ask)
+static inline void node_alloc(node_buffer* b, int ask)
 {
     int space = b->size - b->used_back - b->used_front;
     if (space >= ask) return;
@@ -300,7 +296,7 @@
     b->alloc = buf2;
 }
 
-void static inline attr_alloc(attr_buffer* b, int ask)
+static inline void attr_alloc(attr_buffer* b, int ask)
 {
     int space = b->size - b->used;
     if (space >= ask) return;
@@ -314,7 +310,7 @@
     b->alloc = buf2;
 }
 
-void set_error(document* d, char* msg)
+void set_error(document* d, const char* msg)
 {
     if (d->error_message != NULL) return; // keep the first error message
     d->error_message = malloc(strlen(msg)+1);
@@ -324,7 +320,7 @@
 
 // you now expect a name, perhaps preceeded by whitespace
 // the name may be empty
-str static inline parse_name(document* d)
+static inline str parse_name(document* d)
 {
     int start = doc_position(d);
     if (!is_name1(peek(d)))
@@ -335,14 +331,15 @@
     return start_end(start, doc_position(d));
 }
 
-str static inline parse_attrval(document* d)
+static inline str parse_attrval(document* d)
 {
     trim(d);
-    if (get(d) != '=')
+    if (peek(d) != '=')
     {
         set_error(d, "Expected = in attribute, but missing");
         return start_length(0, 0);
     }
+    skip(d, 1);
     trim(d);
     char c = peek(d);
     if (c == '\"' || c == '\'')
@@ -368,10 +365,10 @@
 
 // seen a tag name, now looking for attributes terminated by >
 // puts the attributes it finds in the attribute buffer
-str static inline parse_attributes(document* d)
+static inline str parse_attributes(document* d)
 {
     int start = d->attrs.used;
-    for (int i = 0; ; i++)
+    while (d->error_message == NULL)
     {
         trim(d);
         str name = parse_name(d);
@@ -388,7 +385,7 @@
 
 
 // Add a new entry into tag, am at a '<'
-void static inline parse_tag(document* d)
+static inline void parse_tag(document* d)
 {
     node_alloc(&d->nodes, 1);
     d->nodes.used_back++;
@@ -399,12 +396,18 @@
     assert(c == '<');
     if (peek(d) == '?') skip(d, 1);
     me->name = parse_name(d);
+    if (me->name.length == 0)
+    {
+        set_error(d, "Missing tag name");
+        return;
+    }
     me->attrs = parse_attributes(d);
+    if (d->error_message != NULL) return;
 
-    c = get(d);
-    if ((c == '/' || c == '?') && peek(d) == '>')
+    c = peek(d);
+    if ((c == '/' || c == '?') && peekAt(d, 1) == '>')
     {
-        skip(d, 1);
+        skip(d, 2);
         me->nodes = start_length(0, 0);
         me->outer.length = start_end(me->outer.start, doc_position(d)).length;
         me->inner = start_length(doc_position(d), 0);
@@ -415,6 +418,8 @@
         set_error(d, "Gunk at the end of the tag");
         return;
     }
+    else
+        skip(d, 1);
     me->inner.start = doc_position(d);
     str content = parse_content(d);
 
@@ -471,7 +476,7 @@
                 return start_end(0, 0);
             }
             skip(d, 2);
-            while (1)
+            for (;;)
             {
                 if (!find(d, '>'))
                 {
@@ -507,7 +512,7 @@
     node nodes[500];
 } buffer;
 
-document* document_parse(char* s, int slen)
+document* document_parse(const char* s, int slen)
 {
     if (slen == -1) slen = (int) strlen(s);
     assert(s[slen] == 0);
@@ -540,9 +545,7 @@
     str content = parse_content(d);
     d->nodes.nodes[0].nodes = content;
 
-    if (d->cursor < d->end && d->error_message == NULL)
-    {
+    if (d->cursor < d->end)
         set_error(d, "Trailing junk at the end of the document");
-    }
     return d;
 }
diff --git a/cbits/hexml.h b/cbits/hexml.h
--- a/cbits/hexml.h
+++ b/cbits/hexml.h
@@ -30,7 +30,7 @@
 // Parse a document, returns a potentially invalid document - use document_error to check
 // Must use document_free to release the memory (even if invalid).
 // The string must be nul terminated, e.g. slen != -1 ==> slen[0]
-document* document_parse(char* s, int slen);
+document* document_parse(const char* s, int slen);
 
 // Free the memory returned from document_parse.
 void document_free(document* d);
@@ -38,19 +38,19 @@
 // Generate a fresh string with the same semantics as the node.
 // Requires an input buffer, returns the size of the rendered document.
 // Requires the string passed to document_parse to be valid.
-int node_render(document* d, node* n, char* buffer, int length);
+int node_render(const document* d, const node* n, char* buffer, int length);
 
 // Return either NULL (successful parse) or the error message.
-char* document_error(document* d);
+char* document_error(const document* d);
 
 // Return the root node of the document - imagine the document is wrapped in <>$DOC</> tags.
-node* document_node(document* d);
+node* document_node(const document* d);
 
 // List all items within a node
-node* node_children(document* d, node* n, int* res);
-attr* node_attributes(document* d, node* n, int* res);
+node* node_children(const document* d, const node* n, int* res);
+attr* node_attributes(const document* d, const node* n, int* res);
 
 // Search for given strings within a node, note that prev may be NULL
 // Requires the string passed to document_parse to be valid.
-node* node_childBy(document* d, node* parent, node* prev, char* s, int slen);
-attr* node_attributeBy(document* d, node* n, char* s, int slen);
+node* node_childBy(const document* d, const node* parent, const node* prev, const char* s, int slen);
+attr* node_attributeBy(const document* d, const node* n, const char* s, int slen);
diff --git a/hexml.cabal b/hexml.cabal
--- a/hexml.cabal
+++ b/hexml.cabal
@@ -1,7 +1,7 @@
 cabal-version:      >= 1.18
 build-type:         Simple
 name:               hexml
-version:            0.1
+version:            0.2
 license:            BSD3
 license-file:       LICENSE
 category:           Development
@@ -30,9 +30,9 @@
     default-language:   Haskell2010
 
     build-depends:
-        base > 4 && < 5,
+        base >= 4.5 && < 5,
         bytestring,
-        extra
+        extra >= 0.3
 
     c-sources:        cbits/hexml.c
     include-dirs:     cbits
