Input Validation

Most web applications process input from forms at some point in their execution. While under some circumstances it may be okay to accept incoming data as is, the chance of malevolent data, even from trusted networks, as well as the extent to which such data can compromise a system (through the use of escape, shell and other sequences), makes doing so in today’s Internet an extremely bad practice.

A first line of defense taken by Ruby is to flag all incoming data as tainted, and restrict the use of such flagged data in potentially dangerous operations if the $SAFE global variable is non-zero. This “safe” level is set to 1 by default in mod_ruby, and it is recommended that it be kept at that setting for Arrow applications as well. In order to use data which comes from outside of the application, then, it is necessary to untaint it, which means to analyze it for acceptability.

The analysis step is typically implemented in Ruby with a regular expression, which is applied to the input, and the part of the input which matches the expected pattern is retained.

In addition to untainting, input data must also sometimes be tested against more-stringent validation routines, such as in the case of credit card numbers, phone numbers, email addresses, etc.

These data-validation tasks are common enough to web application development that Arrow integrates Travis Whitton’s excellent FormValidator library, which provides both validation and (optionally) untainting. It has built in support for validating many different kinds of structured data, giving an application plenty of control over what values are acceptable for any given field with a convenient and consistent interface.

Writing a Validation Specification

The documentation for FormValidator gives a thorough coverage of the different options that are available to control how data gets through. This will be a brief coverage of some of these, with notes on the specifics of how this package is integrated with Arrow and how it can be used by applications you write.

To begin with, recognize that any action which expects to receive input must declare a specification for it to get through. Further, a specification must validate every field of the expected input, or individual fields will not be passed in. To simplify this, you may end up writing a few base validation schemes that then get extended by each of the actions.

The specifications are kept in the signature of an application, stored as a Hash keyed by action name, with values of that action’s specification. While it is acceptable to include all of your actions’ specifications when the signature is first declared, you may find it more readable to include an action’s specification immediately before or after the action itself, thus keeping related information together. (This is equally applicable to templates, config(?) and monitors(?).)

Application With Input Args

   1  class Args < Arrow::Application
   2  
   3      applet_name "Argument Tester"
   4      applet_description "This app is for testing/debugging the argument validator."
   5      applet_maintainer "god@host.com"
   6      
   7      default_action :display
   8  
   9      templates => {
  10              :display    => 'args-display.tmpl',
  11          },
  12          :vargs => {
  13              :display    => {
  14                  :required       => :name,
  15                  :optional       => [:email, :description],
  16                  :filters        => [:strip, :squeeze],
  17                  :constraints    => {
  18                      :email          => :email,
  19                      :name           => /^[\x20-\x7f]+$/,
  20                      :description    => /^[\x20-\x7f]+$/,
  21                  },
  22              },
  23          },
  24      }
  25  
  26      ### All of the applet's functionality is handled by the fallback action
  27      ### (Arrow::Applet#action_missing_action), which loads the 'display'
  28      ### template and renders it.  Cute, huh?
  29  
  30  end # class Args
  31  # ~> -:10: syntax error, unexpected tASSOC, expecting kEND
  32  # ~>     templates => {
  33  # ~>                 ^
  34  # ~> -:12: syntax error, unexpected ',', expecting kEND
  35  # ~> -:24: syntax error, unexpected ',', expecting kEND
Argument testing application.

Then in the template:

   1  <html>
   2      <body>
   3      <h1>Argument Validation/Untainting/Testing applet</h1>
   4      <p>This is an applet to test argument validation.</p>
   5  
   6      <form action="[?call txn.action?]" method="get">
   7          <p>Name: <input id="name-field" type="text" name="name" 
   8            value="[?call txn.request.param('name') ?]" size="20"/></p>
   9          <p>Email: <input id="email-field" type="text" name="email"
  10              value="[?call txn.request.param('email') ?]" size="35"/></p>
  11          <p>Description: <input id="description-field" type="text"
  12              name="description" value="[?call
  13            txn.request.param('description') ?]" size="55"/></p>
  14          <p>Other: <input id="other-field" type="text" name="other"
  15                value="[?call txn.request.param('other') ?]" size="55" /></p>
  16      </form>
  17  
  18      <h2>Valid Args</h2>
  19  
  20      <p>Fetched via: <tt>txn.vargs.valid</tt>:</p>
  21      <tt><?escape txn.vargs.valid.inspect ?></tt>
  22  
  23      <h2>Missing Args</h2>
  24  
  25      <p>Fetched via: <tt>txn.vargs.missing</tt>:</p>
  26      <tt><?escape txn.vargs.missing.inspect ?></tt>
  27  
  28      <h2>Invalid Args</h2>
  29  
  30      <p>Fetched via: <tt>txn.vargs.invalid</tt>:</p>
  31      <tt><?escape txn.vargs.invalid.inspect ?></tt>
  32  
  33      <h2>Unknown Args</h2>
  34  
  35      <p>Fetched via: <tt>txn.vargs.unknown</tt>:</p>
  36      <tt><?escape txn.vargs.unknown.inspect ?></tt>
  37      </body>
  38  </html>
Argument testing template.