<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[andrew makes things]]></title>
  <link href="http://blog.andrewcantino.com/atom.xml" rel="self"/>
  <link href="http://blog.andrewcantino.com/"/>
  <updated>2015-04-17T10:30:13-07:00</updated>
  <id>http://blog.andrewcantino.com/</id>
  <author>
    <name><![CDATA[Andrew Cantino]]></name>
    
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[A RailsConf 2015 Party and Event List]]></title>
    <link href="http://blog.andrewcantino.com/blog/2015/04/15/railsconf-2015-party-list/"/>
    <updated>2015-04-15T12:59:04-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2015/04/15/railsconf-2015-party-list</id>
    <content type="html"><![CDATA[<p>Inspired by <a href="http://www.mikeperham.com/2013/04/11/railsconf-2013-events/">Mike Perham&rsquo;s RailsConf 2013 event list</a>, and not seeing one yet for this year&rsquo;s <a href="http://railsconf.com/">RailsConf</a>, I decided to put one together.  Please send me a note on <a href="https://twitter.com/tectonic">Twitter</a> if you have a party or event to add!</p>

<p><em>Last updated: Friday, April 17th.</em></p>

<h2>Sunday</h2>

<p><a href="https://www.bridgetroll.org/events/150">RailsBridge Installfest</a> (2:00 PM to 5:00 PM)<br></p>

<h2>Monday</h2>

<p><a href="https://www.bridgetroll.org/events/150">RailsBridge Ruby on Rails Workshop</a> (9:00 AM to 6:00 PM)<br></p>

<h2>Tuesday</h2>

<p><a href="https://www.eventbrite.com/e/big-nerd-ranch-hack-night-railsconf-edition-tickets-16540850128">Big Nerd Ranch Hack Night: RailsConf Edition</a> (7:00 PM to 10:00 PM)<br>
<a href="https://www.eventbrite.com/e/railsconf-ruby-karaoke-tickets-16565911086">RailsConf Ruby Karaoke</a> (8:30 PM to 11:30 PM)<br></p>

<h2>Wednesday</h2>

<p><a href="http://railsconf.com/schedule">Official Happy Hour: Exhibit Hall 203</a> (4:10 PM to 5:30 PM)<br>
<a href="http://railsconf.com/schedule">Lightning Talks: Exhibit Hall 202</a> (5:30 PM to 7:30 PM)<br>
<a href="http://www.eventbrite.com/e/hired-atlanta-launch-party-railsconf-2015-happy-hour-tickets-16408022838?aff=es2">Hired Atlanta Launch Party &amp; RailsConf 2015 Happy Hour</a> (6:30 PM to 9:30 PM)<br>
<a href="https://www.eventbrite.com/e/engine-yard-railsconf-after-party-tickets-16499542576?invite=&amp;err=29&amp;referrer=&amp;discount=&amp;affiliate=&amp;eventpassword=">Engine Yard RailsConf After Party</a> (7:30 PM to 10:30 PM)<br></p>

<h2>Thursday</h2>

<p>???</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Using Huginn Scenarios to Save Money]]></title>
    <link href="http://blog.andrewcantino.com/blog/2014/09/13/using-huginn-scenarios-to-save-money/"/>
    <updated>2014-09-13T11:19:40-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2014/09/13/using-huginn-scenarios-to-save-money</id>
    <content type="html"><![CDATA[<p>This is my fourth post about <a href="https://github.com/cantino/huginn">Huginn</a>, a tool that I&rsquo;ve been working on with the generous support of other open source collaborators. Huginn is a light-weight platform for building data-gathering and data-reacting tasks for everyday life. Think of it as an open source Yahoo! Pipes, IFTTT, or Zapier.</p>

<p>In this post I will show you how to setup money-saving deal alerts with Huginn, and then share those alerts with other Huginn users using our new Scenarios system.</p>

<p><strong>Problem</strong>: I love deal sites, but don&rsquo;t want to check them every day.<br />
<strong>Solution</strong>: I&rsquo;ll let Huginn keep an eye on deal sites, and alert me when new interesting deals are available.</p>

<!--more-->


<p>Since we&rsquo;re planning to share this alert with our friends, let&rsquo;s start by making a new Huginn Scenario.  In Huginn, Scenarios are useful for two things: 1) grouping sets of Huginn Agents for easy navigation, and 2) sharing sets of Agents.</p>

<p>Here are some of my Scenarios:</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/slickdeals-post/some-scenarios.png"></p>

<p>Okay, let&rsquo;s make a new shared Scenario to hold Agents that monitor Slickdeals.</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/slickdeals-post/new-scenario.png"></p>

<p>Now, make a new RssAgent to consume the Slickdeals data.  For the <code>url</code>, use <code>http://feeds.feedburner.com/SlickdealsnetFP?format=atom</code>.</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/slickdeals-post/new-rss-agent.png"></p>

<p>I set my RssAgent to consume the Slickdeals RSS feed once an hour, emitting new events for each entry.  I only keep the events for 7 days, since it will save space, and I doubt I&rsquo;ll want to look back at them.  Notice that I put the new Agent in the Scenario that I had just created.</p>

<p>I clicked &ldquo;Run&rdquo; on the new RssAgent and gave it a moment to create its first batch of events.  I clicked on the Events link in the Agent and viewed one of the new events:</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/slickdeals-post/slickdeals-event.png"></p>

<p>Based on this, let&rsquo;s now make a TriggerAgent to watch the RSS feed for interesting items:</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/slickdeals-post/new-trigger-agent.png"></p>

<p>For the options, I did something like this:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='json'><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="nt">&quot;expected_receive_period_in_days&quot;</span><span class="p">:</span> <span class="s2">&quot;30&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;rules&quot;</span><span class="p">:</span> <span class="p">[</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>      <span class="nt">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;regex&quot;</span><span class="p">,</span>
</span><span class='line'>      <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="s2">&quot;skyrim|fandango&quot;</span><span class="p">,</span>
</span><span class='line'>      <span class="nt">&quot;path&quot;</span><span class="p">:</span> <span class="s2">&quot;title&quot;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>  <span class="p">],</span>
</span><span class='line'>  <span class="nt">&quot;message&quot;</span><span class="p">:</span> <span class="s2">&quot;{{title}}: {{urls | first}}&quot;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>This configuration should trigger on any title that has the word &ldquo;skyrim&rdquo; or &ldquo;fandango&rdquo; in it.  I did this because I&rsquo;m looking for a deal on the Skyrim game, and also for discounted movie ticket deals.  You can do anything you&rsquo;d like here!  Notice that I used the Liquid syntax <code>{{urls | first}}</code> to grab the first url from the event, based on the fact (visible in the Event image above) that it contains an array of urls.</p>

<p>Finally, I wired this to an email Agent so that I receive quick emails when new deals show up:</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/slickdeals-post/email-agent.png"></p>

<p>Happy with this new Scenario, I copied the URL from the &ldquo;Share&rdquo; page of the Scenario and sent it to my friends! :)</p>

<p>Questions?  <a href="https://twitter.com/tectonic">Follow me on Twitter</a>!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[An Example of Poor Security Communication in the Google Auth Flow]]></title>
    <link href="http://blog.andrewcantino.com/blog/2014/09/08/example-of-poor-security-communication-in-google-auth-flow/"/>
    <updated>2014-09-08T12:16:00-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2014/09/08/example-of-poor-security-communication-in-google-auth-flow</id>
    <content type="html"><![CDATA[<h2>Responsible Disclosure</h2>

<p>The issues discussed in this post were <a href="http://en.wikipedia.org/wiki/Responsible_disclosure">responsibly disclosed</a> to Google Security.  Google triaged the issues, talked to the involved teams, and declined the opportunity to fix before publication.  They gave me written permission to blog about this.</p>

<h1>The Attack</h1>

<p><strong>Summary</strong>: Google Apps Script is a powerful scripting environment provided by Google that can make authenticated requests against user data inside of Google&rsquo;s properties.  When authorizing a Google Apps Script, users are unfortunately not clearly told that they&rsquo;re allowing a 3rd party access to their data until it&rsquo;s too late, making social engineering attacks far too easy.  Worse, Google Apps Scripts are on a Google domain, so even savvy users who look for suspicious domains will be fooled.  After authorization, the script can do something malicious, such as upload the user&rsquo;s email, delete data, or access sensitive personal information via a Google API.</p>

<!-- more -->


<p><strong>How it works</strong>:</p>

<p>Let&rsquo;s walk through the flow.  Here is a simple Google Apps Script that I made.  Notice the trust-worthy <em>google.com</em> domain.</p>

<p><img src="http://blog.andrewcantino.com/images/posts/security/auth-flow/step1.png"></p>

<p>A malicious person could make an Apps Script that performs almost any action against a user&rsquo;s Google data, then share the link in the guise of a helpful tool.  Since the URL will be to <em>script.google.com</em>, it looks legitimate and even savvy users will likely be fooled.</p>

<p>I named my app &ldquo;Google Security Upgrader&rdquo;, but it could be called &ldquo;New Gmail&rdquo;.</p>

<p>Here is an example authorization flow that a victim sees:</p>

<p><img src="http://blog.andrewcantino.com/images/posts/security/auth-flow/step2.png"></p>

<p>The attacker&rsquo;s ability to name the app anything they want is very dangerous.  I named the app &ldquo;Google Security Upgrader&rdquo;.  Google <em>in no way</em> makes it clear that this app was created by a 3rd party, and is not affiliated with Google.</p>

<p>The user approves the app, because it seems completely legit:</p>

<p><img src="http://blog.andrewcantino.com/images/posts/security/auth-flow/step3.png"></p>

<p>Once approved, my particular script actually just makes a new Gmail label, but it could have deleted data, emailed a link to the script to everyone in the user&rsquo;s contact list, manipulated personal information, or stolen data and sent it to a 3rd party.</p>

<p>Effective security communication is really hard, but it&rsquo;s critical to communicate to a user what actions they&rsquo;re taking, especially during an authorization flow.  At minimum, I feel that Google should add a very prominent warning, both at the top of the script page and in the authorization box.</p>

<p>As it is, I feel it is unreasonable to expect that users would understand the possibility that malicious code could be executed while remaining entirely within the Google domain.  Ironically, <em>after</em> authorization, Google sends an email explaining that a 3rd party app has been authorized, but at this point it&rsquo;s way, way too late! The app has already accessed the user&rsquo;s data, and has deleted, stolen, or manipulated it.</p>

<h2>Additional details</h2>

<p>The test app in this case is called &ldquo;Google Security Upgrader&rdquo; and simply contains:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">doGet</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">GmailApp</span><span class="p">.</span><span class="nx">createLabel</span><span class="p">(</span><span class="s2">&quot;FOO&quot;</span><span class="p">)</span>
</span><span class='line'>  <span class="k">return</span> <span class="nx">HtmlService</span><span class="p">.</span><span class="nx">createHtmlOutput</span><span class="p">(</span><span class="s1">&#39;We deleted all your email and stuff. Have a nice day!&#39;</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h2>Google&rsquo;s Response</h2>

<p>Google chose not to fix this issue before publication: &ldquo;The team will take this suggestion into consideration, but per our discussion with them, this is currently working as designed and is not a technical vulnerability.  Thanks again for the report, and good luck in your future bug hunting!&rdquo;</p>

<p>Unlike my recent <a href="http://blog.andrewcantino.com/blog/2014/09/04/demasking-google-users-with-a-timing-attack/">report to Google about a timing attack</a>, I think this particular issue <em>is</em> severe and should be fixed.  I hope they do so soon.</p>

<h2>More about web security</h2>

<p>If you enjoyed this article, you should <a href="https://twitter.com/tectonic">follow me on Twitter</a> for more.</p>

<p>More articles I&rsquo;ve written about web security and Google:</p>

<ul>
<li><a href="http://blog.andrewcantino.com/blog/2014/09/04/demasking-google-users-with-a-timing-attack/">Demasking Google Users with a Timing Attack</a></li>
<li><a href="http://blog.andrewcantino.com/blog/2011/12/14/hacking-google-for-fun-and-profit/">Hacking Google for Fun and Profit</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Demasking Google Users With a Timing Attack]]></title>
    <link href="http://blog.andrewcantino.com/blog/2014/09/04/demasking-google-users-with-a-timing-attack/"/>
    <updated>2014-09-04T13:34:10-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2014/09/04/demasking-google-users-with-a-timing-attack</id>
    <content type="html"><![CDATA[<h2>Responsible Disclosure</h2>

<p>I believe strongly in the <a href="http://en.wikipedia.org/wiki/Responsible_disclosure">responsible disclosure</a> of security issues, <a href="http://blog.andrewcantino.com/blog/2011/12/14/hacking-google-for-fun-and-profit/">having participated in Google&rsquo;s responsible disclosure program in the past</a> and helping to run a similar <a href="https://hackerone.com/mavenlink">disclosure program</a> at Mavenlink.</p>

<p>The issues discussed in this post were responsibly disclosed to Google Security.  Google triaged the issues, talked to the involved teams, and declined the opportunity to fix.  They gave me written permission to blog about this.</p>

<h1>The Attack</h1>

<p><strong>Summary</strong>: A 3rd party site can determine if a website viewer has access to a particular Google Drive document.</p>

<p><strong>Implications</strong>: An attacker could share a document with one or more email addresses, but uncheck the option that causes Google to send a notification.  Now the attacking site can figure out when someone logged into any of the shared addresses visits their site.  This is mostly useful for very targeted attacks, where an attacking site needs to behave differently based on who is viewing.  This could be used for spear phishing, identification of government officials, demasking users of TOR, industrial mischief, etc.</p>

<!-- more -->


<p><strong>How it works</strong>:  The attack is straightforward. A malicious page repeatedly instantiates an image whose source points at the URL of a Google Drive document.  If that document is viewable by the visitor, loading the resulting page will take longer than if the document is not viewable.  Since the result isn&rsquo;t an image, the <code>onerror</code> callback of the image is triggered in both cases, but we can record how long it takes from image instantiation to triggering of the <code>onerror</code>.  This time will be greater when the document is accessible.  In my experiments, loading took an average of 891ms when the document was available, but 573ms when it was not.  Since this is going to be connection-dependent, it makes sense to simultaneously test against a document that is always known to be inaccessible, then compare times with the probe document.</p>

<p>Google chose not to fix this issue, as &ldquo;the risk here is fairly low, both in terms of impact and difficulty of exploiting this against a large population, and we don&rsquo;t have an effective solution&rdquo;.  I don&rsquo;t really disagree with them&mdash; this <em>is</em> hard to fix, and fairly theoretical.  Still, I think this is an interesting example of a timing attack, and shows how hard these sorts of issues can be to avoid.</p>

<p>Here is some example code:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">var</span> <span class="nx">urls</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">hasAccess</span><span class="o">:</span> <span class="s2">&quot;https://docs.google.com/document/....../edit&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">doesNotHaveAccess</span><span class="o">:</span> <span class="s2">&quot;https://docs.google.com/document/....../edit&quot;</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="kd">function</span> <span class="nx">addImage</span><span class="p">(</span><span class="nx">src</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">elem</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">&quot;IMG&quot;</span><span class="p">);</span>
</span><span class='line'>  <span class="nx">elem</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="nx">src</span> <span class="o">+</span> <span class="s2">&quot;?r=&quot;</span> <span class="o">+</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">();</span>
</span><span class='line'>  <span class="nx">elem</span><span class="p">.</span><span class="nx">onerror</span> <span class="o">=</span> <span class="nx">callback</span><span class="p">;</span>
</span><span class='line'>  <span class="nx">$</span><span class="p">(</span><span class="s2">&quot;body&quot;</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span><span class="nx">elem</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="kd">var</span> <span class="nx">times</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">hasAccess</span><span class="o">:</span> <span class="p">{</span> <span class="nx">sum</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">count</span><span class="o">:</span> <span class="mi">0</span> <span class="p">},</span>
</span><span class='line'>  <span class="nx">doesNotHaveAccess</span><span class="o">:</span> <span class="p">{</span> <span class="nx">sum</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">count</span><span class="o">:</span> <span class="mi">0</span> <span class="p">}</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="kd">var</span> <span class="nx">testRuns</span> <span class="o">=</span> <span class="mi">40</span><span class="p">;</span> <span class="c1">// a smaller number can be used</span>
</span><span class='line'>
</span><span class='line'><span class="kd">function</span> <span class="nx">nextTest</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">testRuns</span><span class="o">--</span><span class="p">;</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="nx">testRuns</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mf">0.5</span> <span class="o">?</span> <span class="s1">&#39;hasAccess&#39;</span> <span class="o">:</span> <span class="s1">&#39;doesNotHaveAccess&#39;</span><span class="p">;</span>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">startTime</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">getTime</span><span class="p">();</span>
</span><span class='line'>    <span class="nx">addImage</span><span class="p">(</span><span class="nx">urls</span><span class="p">[</span><span class="nx">type</span><span class="p">],</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>      <span class="kd">var</span> <span class="nx">endTime</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">getTime</span><span class="p">();</span>
</span><span class='line'>      <span class="nx">times</span><span class="p">[</span><span class="nx">type</span><span class="p">].</span><span class="nx">count</span><span class="o">++</span><span class="p">;</span>
</span><span class='line'>      <span class="nx">times</span><span class="p">[</span><span class="nx">type</span><span class="p">].</span><span class="nx">sum</span> <span class="o">=</span> <span class="nx">times</span><span class="p">[</span><span class="nx">type</span><span class="p">].</span><span class="nx">sum</span> <span class="o">+</span> <span class="p">(</span><span class="nx">endTime</span> <span class="o">-</span> <span class="nx">startTime</span><span class="p">);</span>
</span><span class='line'>      <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">nextTest</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span> <span class="c1">// a shorter timeout is fine</span>
</span><span class='line'>    <span class="p">});</span>
</span><span class='line'>  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">$</span><span class="p">(</span><span class="s2">&quot;body&quot;</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span><span class="s2">&quot;hasAccess: &quot;</span> <span class="o">+</span> <span class="p">(</span><span class="nx">times</span><span class="p">.</span><span class="nx">hasAccess</span><span class="p">.</span><span class="nx">sum</span> <span class="o">/</span> <span class="nx">times</span><span class="p">.</span><span class="nx">hasAccess</span><span class="p">.</span><span class="nx">count</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&quot;&lt;br /&gt;&quot;</span> <span class="o">+</span> <span class="s2">&quot;doesNotHaveAccess: &quot;</span> <span class="o">+</span> <span class="p">(</span><span class="nx">times</span><span class="p">.</span><span class="nx">doesNotHaveAccess</span><span class="p">.</span><span class="nx">sum</span> <span class="o">/</span> <span class="nx">times</span><span class="p">.</span><span class="nx">doesNotHaveAccess</span><span class="p">.</span><span class="nx">count</span><span class="p">));</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">nextTest</span><span class="p">()</span>
</span></code></pre></td></tr></table></div></figure>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Select Any Procfile on Heroku via Environmental Variable]]></title>
    <link href="http://blog.andrewcantino.com/blog/2014/07/26/select-any-procfile-on-heroku-via-environmental-variable/"/>
    <updated>2014-07-26T13:45:53-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2014/07/26/select-any-procfile-on-heroku-via-environmental-variable</id>
    <content type="html"><![CDATA[<p>I couldn&rsquo;t figure out a way to customize which Procfile is run on Heroku, so I made a very simple buildpack that moves your preferred Procfile into position on deploy.</p>

<p><a href="https://github.com/cantino/heroku-selectable-procfile">https://github.com/cantino/heroku-selectable-procfile</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Frequency Analysis of XKCD's 'What If?']]></title>
    <link href="http://blog.andrewcantino.com/blog/2014/07/05/frequency-analysis-of-xkcds-what-if/"/>
    <updated>2014-07-05T09:06:16-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2014/07/05/frequency-analysis-of-xkcds-what-if</id>
    <content type="html"><![CDATA[<p>I thought it&rsquo;d be fun to do a word frequency analysis of XKCD author Randall Munroe&rsquo;s wonderful <a href="https://what-if.xkcd.com/">&ldquo;What If?&rdquo;</a> series.</p>

<!-- more -->


<p>After tokenizing all current 102 &ldquo;What If?&rdquo; entries and removing common <a href="http://en.wikipedia.org/wiki/Stop_words">stop words</a>, the following words were some of the most common:</p>

<pre><code>speed - 181 occurrences
surface - 143 occurrences
energy - 125 occurrences
meters - 119 occurrences
second - 119 occurrences
space - 91 occurrences
pressure - 81 occurrences
still - 81 occurrences
million - 80 occurrences
moon - 80 occurrences
average - 80 occurrences
</code></pre>

<p>Okay, that&rsquo;s fun, but not super surprising.  To dig deeper, I switched to counting the number of <em>different</em> entries that a given word shows up in, instead of just the total occurrence count.</p>

<p>It starts with some equivocation</p>

<pre><code>probably - 73 entries
around - 69 entries
might - 63 entries
</code></pre>

<p>then moves on to physics and time</p>

<pre><code>earth - 56 entries
world - 51
hard - 50
years - 50
speed - 46
surface - 45
meters - 44
air - 43
speed - 43
water - 42
ground - 35
problem - 35
energy - 34
atmosphere - 33
matter - 33
space - 33
billion - 32
force - 30
gravity - 22
sea - 21
</code></pre>

<p>then to unfortunate outcomes</p>

<pre><code>impact - 21 entries
unfortunately - 18
surprisingly - 18
roughly - 17
die - 17
destroy - 16
population - 16
rapidly - 16
weird - 16
experience - 16
</code></pre>

<p>Then I decided to see how this compares to what we know and love about XKCD.</p>

<p>Well, &lsquo;dinosaur(s)&rsquo; only show up in 9 entries.  Of those, the favorites are tyrannosaurus, sauropods, and spinosaurus, but each of those only shows up in one entry.  Very surprisingly, raptors never show up except in illustrations!</p>

<p>Other popular topics include spacecraft (14 entries), baseball (9 entries), asteroids and meteors (both 9 entries, but not always the same ones), explosions and vaporization (7 entries), nuclear (13) &amp; radiation (7), and hurricanes (8).  Here are some more common topics:</p>

<pre><code>orbit - 19 entries
mountain - 19
moon - 16
island - 14
rocket - 14
spacecraft - 14
oxygen - 12
satellite - 11
atmospheric - 11
launch - 11
lifetime - 10
war - 10
weapon - 9
mach - 9
government - 9
blast - 9
bullet - 8
airplane - 8
hurricane - 8
hydrogen - 8
molecule - 7
trigger - 6
extinction - 6
</code></pre>

<p>I think I&rsquo;m seeing a trend.  Now back to reading <em>What If?</em>!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Adding RSS Feeds to Any Site With Huginn]]></title>
    <link href="http://blog.andrewcantino.com/blog/2014/04/13/adding-rss-feeds-to-any-site-with-huginn/"/>
    <updated>2014-04-13T11:28:15-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2014/04/13/adding-rss-feeds-to-any-site-with-huginn</id>
    <content type="html"><![CDATA[<p>This is my third post about <a href="https://github.com/cantino/huginn">Huginn</a>, a tool that I&rsquo;ve been working on with the generous support of other open source collaborators. Huginn is a light-weight platform for building data-gathering and data-reacting tasks for everyday life. Think of it as an open source Yahoo! Pipes, IFTTT, or Zapier.</p>

<p>In this post I will show you how to create an RSS feed for a website that doesn&rsquo;t have one, using Huginn.</p>

<p><strong>Problem</strong>: Many sites don&rsquo;t have RSS feeds.<br />
<strong>Solution</strong>: Let Huginn watch the site for changes and build a feed for you.</p>

<!--more-->


<p>Let&rsquo;s use (the amazing webcomic) <a href="http://xkcd.com">XKCD</a> as an example.  XKCD doesn&rsquo;t have an RSS feed, so let&rsquo;s create one.</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/xkcd-agent.png"></p>

<p>(If you setup Huginn and ran <code>rake db:seed</code>, you&rsquo;ll already have this Agent.)</p>

<p>Now, let&rsquo;s make a DataOutputAgent to convert events from the XKCD Agent into a RSS feed.</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/xkcd-rss-agent.png"></p>

<p>(When you make a new DataOutputAgent, the default options are actually this example.)</p>

<p>Finally, visit the Agent to see the new RSS URL for your XKCD feed.</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/xkcd-rss-agent-show.png"></p>

<p>And that&rsquo;s it!  Now you have an RSS feed of recent XKCD comics, even though xkcd.com doesn&rsquo;t provide such a feed on its own.  Let this run for a while and a full RSS feed will be built.</p>

<p>If you found this interesting, you should also read my <a href="http://blog.andrewcantino.com/blog/categories/huginn/">previous posts on Huginn</a>.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Know When the World Changes-- With Huginn]]></title>
    <link href="http://blog.andrewcantino.com/blog/2014/03/17/know-when-the-world-changes-with-huginn/"/>
    <updated>2014-03-17T14:04:09-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2014/03/17/know-when-the-world-changes-with-huginn</id>
    <content type="html"><![CDATA[<p>This is my second post about <a href="https://github.com/cantino/huginn">Huginn</a>, a tool that I&rsquo;ve been working on with the generous support of other open source collaborators. Huginn is a light-weight platform for building data-gathering and data-reacting tasks for everyday life. Think of it as an open source Yahoo! Pipes, IFTTT, or Zapier.</p>

<p>In this post I will show you how to setup standing alerts about the world; basically, your Huginn will be able to answer arbitrary requests like &ldquo;Tell me when the date of the next Superbowl is announced&rdquo;, &ldquo;Tell me when we discover gravity waves&rdquo;, or &ldquo;Tell me when there is a tsunami headed toward San Francisco&rdquo;.</p>

<p><strong>Problem</strong>: I often think of events that I&rsquo;d like to be alerted of, but frequently miss them in the news.<br />
<strong>Solution</strong>: Let Huginn watch the news (via Twitter) and alert you when there are spikes of interest around topics that you care about.</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/gravity-waves-detected.png"></p>

<!--more-->


<p>After having <a href="https://github.com/cantino/huginn">setup Huginn on your server</a>, you will need to create some Twitter credentials. Follow <a href="https://github.com/cantino/huginn/wiki/Configuring-OAuth-applications">these instructions on the Huginn Wiki</a> to register a Twitter Application and install it in your Huginn instance by editing the <code>.env</code> file.</p>

<p>Now that you&rsquo;ve finished setting up a Twitter Application and have restarted your Huginn instance, you should visit the Services page and click &ldquo;Authenticate with Twitter&rdquo;.  Twitter will ask you to login and authorize your new Twitter Application.  When you do this, you should see a new Service in Huginn with your Twitter username.</p>

<p>Now, finally, you&rsquo;re ready to make a TwitterStreamAgent! <em>&ldquo;The TwitterStreamAgent follows the Twitter stream in real time, watching for certain keywords, or filters, that you provide.&rdquo;</em>  For this article, we&rsquo;re going to use a new TwitterStreamAgent to watch keywords of interest on Twitter and to tell us, every 30 minutes, how many times each keyword has been seen. This technique works great for common keywords, but not very well for rare ones. If you want to track rare keywords, like a unique product name, you could make a second TwitterStreamAgent and set it to generate <code>events</code> instead of <code>counts</code>, and then have these emailed to you whenever they occur.</p>

<p>Okay, so set your new TwitterStreamAgent to run every <code>30 minutes</code>, keep events for <code>7 days</code>, and generate <code>counts</code>.  For the <code>filters</code> section you could enter <code>superbowl date announced</code>, <code>gravity waves detected</code>, and <code>huginn open source</code>.  Your Agent will keep track of each of these terms independently.</p>

<p>Your screen should now look something like this:</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/new-twitter-stream-agent.png"></p>

<p>Next, let&rsquo;s setup a new PeakDetectorAgent. A PeakDetectorAgent is used to detect rising edges in a stream of data. In our case, we want to look for spikes in the Twitter event counts. The only change from the default here is to put &ldquo;std_multiple&rdquo; to <code>5</code> instead of <code>3</code>. This is up to you, and just tunes how sensitive the agent will be, with higher numbers requiring larger spikes before detection. If you get too many or too few alerts, you can customize this value.  (Here <code>std</code> stands for Standard Deviation.  Your data is likely not actually Gaussian, but STD still makes a nice tuning factor.)</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/new-peak-detector-agent.png"></p>

<p>If you let your Huginn run for a while, wait for some scientific revolutions to occur, and then click &ldquo;Show&rdquo;, you might see something like this:</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/gravity-waves-detected.png"></p>

<p>If you want, you can stop here. This is technically all you need! Just send the output of your new PeakDetectorAgent to an email or email digest agent, and you&rsquo;ll receive alerts as interest spikes on Twitter. However, I recommend adding one more Agent to the flow in order to improve readability: an EventFormattingAgent.</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/new-event-formatting-agent.png"></p>

<p>This Agent will format the JSON output from the peak detector into a more readable format with a link to search Twitter and see what the commotion is about. Now, connect this to an email agent, and you&rsquo;re done!</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/afternoon-digest-agent.png"></p>

<p>This example Huginn Agent flow has a medium response time&mdash; it responds 30-60 minutes after an interest spike starts on Twitter. Some topics demand a faster response time, like &ldquo;san francisco tsunami warning&rdquo;, &ldquo;flash ticket sale&rdquo;, or &ldquo;stock market crashing&rdquo;. To handle alerts like these, I run a different TwitterStreamAgent and PeakDetectorAgent, with the TwitterStreamAgent checking every 2 minutes, the PeakDetectorAgent set to do immediate propagation.  Resulting peaks are sent directly to an email or SMS agent instead of a email digest, so that I get alerted right away.</p>

<p>For more ideas and updates around Huginn, please <a href="https://github.com/cantino/huginn">get involved</a> and <a href="https://twitter.com/tectonic">follow me on Twitter</a>!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Parsing Bash in JavaScript in Chrome With Browserify]]></title>
    <link href="http://blog.andrewcantino.com/blog/2014/02/28/parsing-bash-in-javascript-in-the-browser-with-js-shell-parse-via-browserify/"/>
    <updated>2014-02-28T22:21:48-08:00</updated>
    <id>http://blog.andrewcantino.com/blog/2014/02/28/parsing-bash-in-javascript-in-the-browser-with-js-shell-parse-via-browserify</id>
    <content type="html"><![CDATA[<p>For a side project, I wanted to be able to use <a href="https://github.com/grncdr/js-shell-parse">js-shell-parse</a> to parse complex Bash commands in JavaScript, in a Chrome extension.  (More on this craziness in a future post!)</p>

<p>The js-shell-parse library is targeted at node, and it makes frequent use of <code>require</code> and of various npm packages.  Being a node noob, I hadn&rsquo;t used <a href="http://browserify.org/">Browserify</a> before, but it turned out to be exactly what I needed: a tool to bundle complex dependency chains of node packages for the browser.  Here are the steps to convert js-shell-parse into a single, compiled bundle:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="c"># Clone the repo</span>
</span><span class='line'>git clone https://github.com/grncdr/js-shell-parse.git
</span><span class='line'>
</span><span class='line'><span class="c"># Install the various npm dependencies and browserify (you may need to use sudo)</span>
</span><span class='line'>npm install -g pegjs pegjs-override-action isarray array-map browserify
</span><span class='line'>
</span><span class='line'><span class="c"># Run the included build script</span>
</span><span class='line'>node build.js &gt; js-shell-parse.js
</span><span class='line'>
</span><span class='line'><span class="c"># Create a very simple loader script called &#39;loader.js&#39; that contains one line</span>
</span><span class='line'><span class="nb">echo</span> <span class="s2">&quot;window.jsShellParse = require(&#39;./js-shell-parse&#39;);&quot;</span> &gt; loader.js
</span><span class='line'>
</span><span class='line'><span class="c"># Run browserify on the loader, which will parse the AST and bundle all dependencies</span>
</span><span class='line'>browserify loader.js -o compiled-js-shell-parse.js
</span></code></pre></td></tr></table></div></figure>


<p>Finally, you can include the output in any website!</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">&quot;compiled-js-shell-parse.js&quot;</span><span class="nt">&gt;&lt;/script&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>and try this in the console:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">var</span> <span class="nx">structure</span> <span class="o">=</span> <span class="nx">jsShellParse</span><span class="p">(</span><span class="s1">&#39;echo &quot;The date is: `date`&quot; &gt; output&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">structure</span><span class="p">));</span>
</span><span class='line'><span class="s2">&quot;[{&quot;</span><span class="nx">type</span><span class="s2">&quot;:&quot;</span><span class="nx">command</span><span class="s2">&quot;,&quot;</span><span class="nx">command</span><span class="s2">&quot;:{&quot;</span><span class="nx">type</span><span class="s2">&quot;:&quot;</span><span class="nx">literal</span><span class="s2">&quot;,&quot;</span><span class="nx">value</span><span class="s2">&quot;:&quot;</span><span class="nx">echo</span><span class="s2">&quot;},&quot;</span><span class="nx">args</span><span class="s2">&quot;:[{&quot;</span><span class="nx">type</span><span class="s2">&quot;:&quot;</span><span class="nx">concatenation</span><span class="s2">&quot;,&quot;</span><span class="nx">pieces</span><span class="s2">&quot;:[{&quot;</span><span class="nx">type</span><span class="s2">&quot;:&quot;</span><span class="nx">literal</span><span class="s2">&quot;,&quot;</span><span class="nx">value</span><span class="s2">&quot;:&quot;</span><span class="nx">The</span> <span class="nx">date</span> <span class="nx">is</span><span class="o">:</span> <span class="s2">&quot;},{&quot;</span><span class="nx">type</span><span class="s2">&quot;:&quot;</span><span class="nx">command</span><span class="o">-</span><span class="nx">substitution</span><span class="s2">&quot;,&quot;</span><span class="nx">commands</span><span class="s2">&quot;:[{&quot;</span><span class="nx">type</span><span class="s2">&quot;:&quot;</span><span class="nx">command</span><span class="s2">&quot;,&quot;</span><span class="nx">command</span><span class="s2">&quot;:{&quot;</span><span class="nx">type</span><span class="s2">&quot;:&quot;</span><span class="nx">literal</span><span class="s2">&quot;,&quot;</span><span class="nx">value</span><span class="s2">&quot;:&quot;</span><span class="nx">date</span><span class="s2">&quot;},&quot;</span><span class="nx">args</span><span class="s2">&quot;:[],&quot;</span><span class="nx">redirects</span><span class="s2">&quot;:[],&quot;</span><span class="nx">env</span><span class="s2">&quot;:{},&quot;</span><span class="nx">control</span><span class="s2">&quot;:&quot;</span><span class="p">;</span><span class="s2">&quot;,&quot;</span><span class="nx">next</span><span class="s2">&quot;:null}]}]}],&quot;</span><span class="nx">redirects</span><span class="s2">&quot;:[{&quot;</span><span class="nx">type</span><span class="s2">&quot;:&quot;</span><span class="nx">redirect</span><span class="o">-</span><span class="nx">fd</span><span class="s2">&quot;,&quot;</span><span class="nx">fd</span><span class="s2">&quot;:1,&quot;</span><span class="nx">op</span><span class="s2">&quot;:&quot;</span><span class="o">&gt;</span><span class="s2">&quot;,&quot;</span><span class="nx">filename</span><span class="s2">&quot;:{&quot;</span><span class="nx">type</span><span class="s2">&quot;:&quot;</span><span class="nx">literal</span><span class="s2">&quot;,&quot;</span><span class="nx">value</span><span class="s2">&quot;:&quot;</span><span class="nx">output</span><span class="s2">&quot;}}],&quot;</span><span class="nx">env</span><span class="s2">&quot;:{},&quot;</span><span class="nx">control</span><span class="s2">&quot;:&quot;</span><span class="p">;</span><span class="s2">&quot;,&quot;</span><span class="nx">next</span><span class="s2">&quot;:null}]&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<p>If you&rsquo;re playing with js-shell-parse, <a href="https://github.com/grncdr/js-shell-parse/tree/master/tests">the tests</a> are helpful to see what kinds of shell/bash commands it can parse.  (Pretty much everything!)</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Never Forget Your Umbrella Again, With Huginn]]></title>
    <link href="http://blog.andrewcantino.com/blog/2014/01/12/never-forget-your-umbrella-again-with-huginn/"/>
    <updated>2014-01-12T10:43:00-08:00</updated>
    <id>http://blog.andrewcantino.com/blog/2014/01/12/never-forget-your-umbrella-again-with-huginn</id>
    <content type="html"><![CDATA[<p><a href="https://github.com/cantino/huginn">Huginn</a> is a tool that I&rsquo;ve been working on, with the support of generous open source collaborators, for about a year.  Huginn is a light-weight infrastructure for building data-gathering and data-reacting tasks for your everyday life.  Think of it as an open source Yahoo! Pipes, IFTTT, or Zapier.  It wouldn&rsquo;t surprise me if, in the future, Huginn evolves into something like Google Now, but without the creepiness factor, because you control and host your own data.</p>

<p>I haven&rsquo;t done a very good job of sharing all of the things that can be built with Huginn, but I&rsquo;m resolved to start.</p>

<p>So, in this post, a very simple example:</p>

<p><strong>Problem</strong>: I always forget to check the weather, leave my umbrella at home, and get soaked.<br />
<strong>Solution</strong>: Let Huginn send you an email (or SMS) each morning when rain is expected in your area.</p>

<!--more-->


<p>After having setup Huginn on your server, make a new WeatherAgent:</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/new-agent.png"></p>

<p>You&rsquo;ll just need to give it your zipcode (or location code) and a <a href="http://www.wunderground.com/weather/api/">free API key for Wunderground</a>.  Set the agent to run at 10pm, so that the data it gathers is for tomorrow&rsquo;s weather.</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/sf-weather-agent.png"></p>

<p>Once you&rsquo;ve saved your new WeatherAgent, it&rsquo;s time to setup a TriggerAgent.  You&rsquo;ll want to set its source as the WeatherAgent that you just created.</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/rain-alert-agent.png"></p>

<p>TriggerAgents contain a set of rules, all of which must match for the trigger to fire.  In this case, we only need one rule to match against the <code>conditions</code> property of the Wunderground API weather events.  We&rsquo;ll do a regular expression (<code>regex</code>) match against this property, looking for the words &ldquo;rain&rdquo; or &ldquo;storm&rdquo;.  When the trigger fires, it will output an event with the message &ldquo;Take an umbrella!  It looks like &lsquo;&lt;conditions>&rsquo; tomorrow in &lsquo;&lt;location>&rsquo;&rdquo;.  These &lt;&hellip;> fields will be filled in by the corresponding properties from the events being filtered&mdash; in this case, just events from your WeatherAgent.  This screenshot shows the Agent&rsquo;s options being edited as JSON so that it&rsquo;s easier for you to read.  You can use either editor mode.</p>

<p>Finally, we just need an Agent to email us the TriggerAgent&rsquo;s events.  We&rsquo;ll use a DigestEmailAgent for this because it queues incoming events until a specific time, then sends them all at once.  You could, however, use a normal EmailAgent to send the email right away, or a TwilioAgent to send an SMS.</p>

<p>Set the agent&rsquo;s schedule to run at 6am, so that you wake up to an email.  Set it&rsquo;s source as the Rain Alert agent you just created.</p>

<p><img src="http://blog.andrewcantino.com/images/posts/huginn/digest-email-agent.png"></p>

<p>And that&rsquo;s it!  You&rsquo;ll now receive an email at 6am any morning when rain is expected in your area.  Stay dry!</p>

<p>I&rsquo;ll be posting more examples soon.  Please let me know what sort of Huginn tutorials you&rsquo;d like to see!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Archive a PDF of Your Posterous Blog]]></title>
    <link href="http://blog.andrewcantino.com/blog/2013/03/12/archive-a-pdf-of-your-posterous-blog/"/>
    <updated>2013-03-12T23:23:00-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2013/03/12/archive-a-pdf-of-your-posterous-blog</id>
    <content type="html"><![CDATA[<p>My wife and I had a private travel blog on Posterous.  Unfortunately, Posterous got aquihired by Twitter and is shutting down, so I spent a few minutes figuring out how to save a PDF of the blog.  Chrome and Firefox did pretty poorly at saving a decent looking PDF for my long blog so I installed wkhtmltopdf and made one myself.  Here&rsquo;s how.</p>

<p>Install <a href="http://code.google.com/p/wkhtmltopdf/">wkhtmltopdf</a>, then run:</p>

<pre><code>wkhtmltopdf --enable-plugins --margin-bottom 0 --margin-top 0 --margin-left 0 --margin-right 0 "http://yourblog.com" archive.pdf
</code></pre>

<p>If your blog has a password, log in with a browser and copy the cookie.  Firefox makes this easy in the Developer Toolbar by entering <code>cookie list</code>.</p>

<pre><code>wkhtmltopdf --cookie cookiename cookievalue ...
</code></pre>

<p>Finally, if you&rsquo;re on a Mac and used the pre-built wkhtmltopdf disk image, you can still use the command line.</p>

<pre><code>/path/to/wkhtmltopdf.app/Contents/MacOS/wkhtmltopdf ...
</code></pre>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Command Line Accounting With Ledger and Reckon, an Update]]></title>
    <link href="http://blog.andrewcantino.com/blog/2013/02/16/command-line-accounting-with-ledger-and-reckon/"/>
    <updated>2013-02-16T14:08:00-08:00</updated>
    <id>http://blog.andrewcantino.com/blog/2013/02/16/command-line-accounting-with-ledger-and-reckon</id>
    <content type="html"><![CDATA[<p>I&rsquo;ve been using <a href="http://ledger-cli.org/">ledger</a>, combined with a custom Ruby gem called <a href="https://github.com/cantino/reckon">reckon</a>, to balance my small business&rsquo;s accounts for the <a href="http://blog.andrewcantino.com/blog/2010/11/06/command-line-accounting-with-ledger-and-reckon/">last few years</a>.  The command line, Bayesian statistics, and Double Entry Accounting!  What could be better?  Here&rsquo;s how I do it.</p>

<p>First, I export the year&rsquo;s transaction history from Chase (in my case) and save it as a CSV file called <code>chase-2012.csv</code>.  It looks something like this:</p>

<pre><code>Type,Post Date,Description,Amount
DEBIT,12/31/2012,"ODESK***BAL-27DEC12 650-12345 CA           12/28",-123.45
DEBIT,12/24/2012,"ODESK***BAL-20DEC12 650-12345 CA           12/21",-123.45
DEBIT,12/24/2012,"GH *GITHUB.COM     FP 12345 CA        12/23",-12.00
</code></pre>

<p>Then, I make a new ledger file called <code>2012.dat</code> and start it with:</p>

<pre><code>2012/01/01 * Checking
    Assets:Bank:Checking            $10,000.00
    Equity:Opening Balances
</code></pre>

<p>Where the <code>$10,000.00</code> is the hypothetical starting balance of my bank account on the first day of 2012.  Since I&rsquo;ve been using ledger, this is just the balance of the account from the summary that I generated at the end of 2011.</p>

<p>Now, I run <code>reckon</code>, initially with the <code>-p</code> option to see its analysis of the CSV file:</p>

<pre><code>&gt; reckon -f chase-2012.csv -v -p --contains-header

What is the account name of this bank account in Ledger? |Assets:Bank:Checking| 
I didn't find a high-likelyhood money column, but I'm taking my best guess with column 4.
+------------+------------+----------------------------------------------------------+
| Date       | Amount     | Description                                              |
+------------+------------+----------------------------------------------------------+
| ...        | ...        | ...                                                      |
| 2012/12/24 | -$12.00    | DEBIT; GH *GITHUB.COM FP 12345 CA 12/23                  |
| 2012/12/24 | -$123.45   | DEBIT; ODESK***BAL-20DEC12 650-12345 CA 12/21            |
| 2012/12/31 | -$123.45   | DEBIT; ODESK***BAL-27DEC12 650-12345 CA 12/28            |
+------------+------------+----------------------------------------------------------+
</code></pre>

<p>It looks like <code>reckon</code> has guessed the correct columns from the CSV, so now I run it in &ldquo;learning&rdquo; mode.  It loads in my data from 2011 and uses it to guess at labels for my 2012 data, using a simple Naive Bayes classifier.</p>

<pre><code>&gt; reckon -f chase-2012.csv -v -o 2012.dat -l 2011.dat --contains-header

...
+------------+---------+------------------------------------------------+
| 2012/12/24 | -$12.00 | DEBIT; GH *GITHUB.COM FP 12345 CA 12/23        |
+------------+---------+------------------------------------------------+
To which account did this money go? ([account]/[q]uit/[s]kip) |Expenses:Web Hosting:Github| 
+------------+----------+-------------------------------------------------+
| 2012/12/24 | -$123.45 | DEBIT; ODESK***BAL-20DEC12 650-12345 CA 12/21   |
+------------+----------+-------------------------------------------------+
To which account did this money go? ([account]/[q]uit/[s]kip) |Expenses:Programming| 
+------------+----------+-------------------------------------------------+
| 2012/12/31 | -$123.45 | DEBIT; ODESK***BAL-27DEC12 650-12345 CA 12/28   |
+------------+----------+-------------------------------------------------+
To which account did this money go? ([account]/[q]uit/[s]kip) |Expenses:Programming|
</code></pre>

<p>In each of these cases, the Bayesian classifier correctly guessed the appropriate label for these expenses based on last year&rsquo;s data.</p>

<p>Now, with a fully-updated 2012.dat file, I run it through ledger and get the following hypothetical results:</p>

<pre><code>&gt; ledger -f 2012.dat -s bal

    $20,000   Assets:Bank:Checking
   $-10,000   Equity:Opening Balances
    $258.90   Expenses
    $246.90     Programming
     $12.00     Web Hosting
     $12.00       Github
   $-10,258.90  Income
   $-10,258.90    Some source of income that makes this math work
</code></pre>

<p>I like to do all of this work inside of my Dropbox folder in case I delete or overwrite a file by mistake.</p>

<p>Want to do all of this yourself?  Start by visiting <a href="http://ledger-cli.org/">ledger-cli.org</a>, or by installing ledger with homebrew:</p>

<pre><code>&gt; brew install ledger
</code></pre>

<p>Then install reckon:</p>

<pre><code>&gt; (sudo) gem install reckon
</code></pre>

<p>Have fun!</p>

<p><a href="https://github.com/cantino/reckon">Reckon</a> is available on GitHub, and, for updates, you should <a href="https://twitter.com/tectonic">follow me on Twitter</a>.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Running Ruby Inside of Ruby (in the Best Way Ever)]]></title>
    <link href="http://blog.andrewcantino.com/blog/2013/01/01/running-ruby-inside-of-ruby-in-the-best-way-ever/"/>
    <updated>2013-01-01T13:39:00-08:00</updated>
    <id>http://blog.andrewcantino.com/blog/2013/01/01/running-ruby-inside-of-ruby-in-the-best-way-ever</id>
    <content type="html"><![CDATA[<p>There are no good Ruby sandboxing options right now.  You can sort of use <code>$SAFE</code> levels and <a href="http://www.ruby-doc.org/docs/ProgrammingRuby/html/taint.html">taint checking</a>, you can sort of use <a href="https://github.com/tario/shikashi">Shikashi</a>, you can use the <a href="http://rubydoc.info/gems/secure">secure gem</a> to run in a separate process, and you can, with much care, use chrooted and jailed virtual machines or <a href="http://lxc.sourceforge.net/">Linux containers</a>.  None of these options met my exacting standards, meaning they&rsquo;re not ridiculous.  Therefore, I&rsquo;m introducing…</p>

<p><strong>RubyOnRuby</strong>!</p>

<p>An unholy amalgam of therubyracer&rsquo;s V8 engine and emscripted-ruby to allow a truly sandboxed Ruby-on-Ruby environment.</p>

<p><a href="https://github.com/cantino/ruby_on_ruby">Check it out on GitHub!</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Make an Expanding Text UI With jQuery Expando]]></title>
    <link href="http://blog.andrewcantino.com/blog/2012/09/21/make-an-expanding-text-ui-with-jquery-expando/"/>
    <updated>2012-09-21T20:34:00-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2012/09/21/make-an-expanding-text-ui-with-jquery-expando</id>
    <content type="html"><![CDATA[<script>
  jQuery(function() {
    jQuery("#expando-example").expando();
  });
</script>


<p><span id="expando-example"><expando><initial>Recently</initial><expanded>Recently, after receiving a couple of <expando><initial>requests</initial><expanded>friendly requests</expanded></expando></expanded></expando>, I extracted the <expando><initial>code</initial><expanded>jQuery code</expanded></expando> powering the <expando><initial>UI</initial><expanded>expanding text UI</expanded></expando> of <a href="http://andrewcantino.com">andrewcantino.com</a>.  You can <expando><initial>get it</initial><expanded>download or fork the source</expanded></expando> on GitHub: <a href="https://github.com/cantino/expando">https://github.com/cantino/expando</a></span></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Compressing Code]]></title>
    <link href="http://blog.andrewcantino.com/blog/2012/06/15/compressing-code/"/>
    <updated>2012-06-15T21:15:00-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2012/06/15/compressing-code</id>
    <content type="html"><![CDATA[<div class='empty'>
  <script type="text/javascript" src="http://www.google.com/jsapi"></script>
  <script type="text/javascript">
    google.load('visualization', '1', {packages: ['corechart']});
  </script>
  <script type="text/javascript">
    function drawVisualization() {
      var languageData = {
        Ruby: [
          ["octopress",3.109],
          ["homebrew",3.310],
          ["will_paginate",3.724],
          ["jekyll",3.909],
          ["cucumber",3.976],
          ["compass",4.076],
          ["bundler",4.087],
          ["sinatra",4.096],
          ["gollum",4.126],
          ["refinerycms",4.215],
          ["resque",4.217],
          ["ruby",4.217],
          ["diaspora",4.285],
          ["active_admin",4.452],
          ["spree",4.634],
          ["devise",4.961],
          ["paperclip",5.087],
          ["rails_admin",5.276],
          ["rails",5.327],
          ["capybara",5.335],
          ["cancan",5.434],
          ["authlogic",5.666],
          ["fog",5.787],
          ["mongoid",6.714]
        ],

        CPP: [
          ["v8",3.031],
          ["node",3.049],
          ["bitcoin",4.037],
          ["wkhtmltopdf",4.081],
          ["mongo",4.264],
          ["scribe",4.504],
          ["mysql",4.977],
          ["doom3.gpl",5.024],
          ["phantomjs",5.501]
        ],

        Java: [
          ["hudson",4.558],
          ["hadoop",4.952],
          ["grails-core",5.466],
          ["clojure",5.930],
          ["cassandra",6.601],
          ["netty",6.692],
          ["voldemort",6.986],
          ["spring-framework",7.043],
          ["storm",9.340]
        ],

        JS: [
          ["Skeleton",1.648],
          ["mustache.js",2.838],
          ["impress.js",3.102],
          ["pdf.js",3.245],
          ["three.js",3.371],
          ["underscore",3.410],
          ["zepto",3.505],
          ["raphael",3.614],
          ["backbone",3.630],
          ["history.js",3.752],
          ["UglifyJS",3.788],
          ["headjs",3.866],
          ["scriptaculous",4.044],
          ["less.js",4.105],
          ["handlebars.js",4.199],
          ["jquery",4.202],
          ["ace",4.207],
          ["bootstrap",4.219],
          ["d3",4.413],
          ["prototype",4.511],
          ["ember.js",4.549],
          ["jasmine",4.872],
          ["async",4.978],
          ["express",5.145],
          ["mongoose",5.735],
          ["socket.io",6.719]
        ],

        PHP: [
          ["WordPress",4.373],
          ["twitteroauth",4.408],
          ["pyrocms",4.559],
          ["foundation",5.067],
          ["Slim",5.552],
          ["symfony",6.337],
          ["zf2",6.580],
          ["cakephp",6.839]
        ],

        C: [
          ["git",3.758],
          ["redis",4.194],
          ["memcached",4.539],
          ["ccv",4.659],
          ["linux",4.847],
          ["yajl",4.983],
          ["nginx",5.352],
          ["php-src",8.752]
        ],

        Python: [
          ["webpy",2.943],
          ["fabric",3.573],
          ["reddit",3.587],
          ["blueprint",3.962],
          ["tornado",4.279],
          ["flask",4.415],
          ["django",4.901]
        ]
      };

      var summary = [
        ["Python",3.952],
        ["JS",4.064],
        ["CPP",4.274],
        ["Ruby",4.584],
        ["C",5.136],
        ["PHP",5.464],
        ["Java",6.396]
      ];

      var crossLanguageSummary = google.visualization.arrayToDataTable([["", ""]].concat(summary.reverse()));
      new google.visualization.BarChart(jQuery("#cross-language").get(0)).draw(crossLanguageSummary,
            {title:"Average Compressability by Language",
              width: 400, height: 300,
              legend: { position: "none" },
              chartArea: { width: "80%", height: "80%" }
            });


      jQuery.each(languageData, function(name, value) {
        var data = google.visualization.arrayToDataTable([["", ""]].concat(languageData[name].reverse()));
        var $elem = jQuery("<div></div>").addClass("visualization").addClass(name).css({ width: "600px", height: "400px", display: "none" });
        jQuery("#visualizations").append($elem);
        jQuery("#viz-links").append(jQuery('<a href="#"></a>').text(name).click(function(e) {
          e.preventDefault();
          jQuery("#visualizations .visualization").hide();
          $elem.show();
          return false;
        }));

        new google.visualization.BarChart($elem.get(0)).draw(data,
               {title:"Compressability of " + name + " Libraries",
                width: 600, height: 500,
                legend: { position: "none" },
                hAxis: { maxValue: 7, minValue: 2, viewWindowMode: 'explicit', viewWindow: { max: 7, min: 2 } },
                chartArea: { width: "70%", height: "75%" }
               });
      });

      jQuery("#visualizations .visualization.Ruby").show();
    }

    google.setOnLoadCallback(drawVisualization);
  </script>

  <style>
    .empty {
      width: 0;
      height: 0;
      padding: 0;
      margin: 0;
    }

    #viz-links a {
      padding: 10px;
    }

    #visualizations {
      width: 600px;
      height: 500px;
    }

    #cross-language {
      width: 400px;
      height: 300px;
    }

    .cross-language-wrapper {
      margin: 8px 20px 10px 20px;
      float: right;
      width: 385px;
      height: 355px;
      padding-left: 5px;
      font-size: 13px;
      font-style: italic;
      text-align: center;
      overflow: hidden;
      background-color: white;
    }

    .box {
      box-shadow: 2px 2px 9px #999;
      border: 1px solid #999;
    }

    .results {
      clear: both;
      margin: 10px;
      padding: 10px;
      width: 600px;
      overflow: hidden;
      height: 500px;
    }
  </style>
</div>


<p>What can we learn about a code base or a language based on its compressibility?  My pet theory is that less compressible code will be, on average, better code, because less compressible code implies more factoring, more reuse, and fewer repetitions.</p>

<div class='cross-language-wrapper box'>
  <div id='cross-language'></div>
  <div>
    Larger numbers (longer lines) indicate more compressibility.
  </div>
</div>


<p>Below are the compressibility results for some popular libraries and languages.  To generate this data, I downloaded each package or library, extracted the source files, removed comments, and gzipped the files with maximum compression.  The numbers represent the ratio of uncompressed source file size to compressed source file size.  Smaller ratios imply less compressible code.</p>

<p>Unsurprisingly, Java was the most compressible language- all that boilerplate!  Python was the least compressible language, with Java, on average, being about twice as compressible.  I was somewhat surprised that JavaScript was the second best language in terms of incompressibility.</p>

<p>It&rsquo;s probably disingenuous to draw strong conclusions from these results, but I still find them intriguing.  What, if anything, do you think they mean?</p>

<div class='results box'>
  <div id="viz-links">
    <span>View data for:</span>
  </div>

  <div id="visualizations"></div>
</div>




<br />


<p><strong>Aside:</strong> Compression itself is a fascinating subject.  Being able to compress something is fundamental to being able to understanding it.  If you can rewrite a 2 page document into 2 paragraphs, while still expressing its core ideas, you&rsquo;ve deeply understood the material.  Hence the existence of the <a href="http://en.wikipedia.org/wiki/Hutter_Prize">Hutter Prize</a>, a standing challenge in Artificial Intelligence to further compress a corpus of English text.  Hence, also, the existence of <a href="http://www.cs.technion.ac.il/~elad/publications/journals/2007/FaceCompress_KSVD_JVCIR.pdf">specialized image compression algorithms</a> that compress human faces better than anything else because they understand, in software, what a human face generally looks like.</p>

<p>If I can compress an image of your face, I can probably also recognize it.  Imagine that I have thousands of photos of faces.  Using some linear algebra and creative encodings, I can figure out the commonalities and differences among these faces.  Basically, I can derive a set of common noses, a set of common eyes, a set of common brows&hellip; and, given a new face photo, I can compute a mixture of these common attributes for the new face.  Perhaps it, roughly speaking, has 60% of Common Nose 6 and 40% of Common Nose 12.  Well, then I can represent the picture of this new nose as roughly two numbers, the &ldquo;amounts&rdquo; of Nose 6 and of Nose 12, and suddenly I&rsquo;ve compressed a collection of hundreds or thousands of pixels- a photo of a nose- into just two numbers.  We could also go in reverse, taking a photo, calculating the percentages of different common features, and then looking up in a database to see who we know whose face expresses those same feature percentages.  We can compress, and, thus, we can recognize.  (Interested in this?  See <a href="http://en.wikipedia.org/wiki/Eigenface">Eigenfaces</a> as well as <a href="http://www.face-rec.org/algorithms/Comparisons/draper_cviu.pdf">PCA and ICA for face analysis</a>.)</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[My Experiences With Personal Outsourcing]]></title>
    <link href="http://blog.andrewcantino.com/blog/2012/06/10/on-lifesourcing/"/>
    <updated>2012-06-10T16:42:00-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2012/06/10/on-lifesourcing</id>
    <content type="html"><![CDATA[<p>Over the last few years I&rsquo;ve been experimenting with outsourcing.  I&rsquo;ve done this both for personal and business projects.  In the personal domain, some people call this &ldquo;lifesourcing&rdquo;: the practice of modularizing and outsourcing parts of your life that you don&rsquo;t enjoy so that you can maximize the parts that you do.  It&rsquo;s outsourcing (with many of the same pros and cons), but for your personal life.</p>

<p>A growing number of sites have popped up recently to facilitate lifesourcing, and while these sites aren&rsquo;t strictly needed- you can still find skilled people to help you on Craigslist, for example- they make this sort of outsourcing even easier.</p>

<p>I&rsquo;d like to talk about some tips and tricks, but first, let me list a few of the things that I have outsourced over the last couple of years.</p>

<h3>Personal Things</h3>

<ul>
<li>My wife and I outsourced hand written, cursive wedding invitations on <a href="http://taskrabbit.com/PAL/29828">TaskRabbit</a>.  (My mother-in-law preferred them to be hand written, my hand writing stinks, and my wife didn&rsquo;t have the time.)</li>
<li>Also for our wedding, someone on <a href="http://fiverr.com">Fiverr</a> polished our save-the-date photo.</li>
<li>After the wedding, we hired a wonderful, well-traveled woman on TaskRabbit to help plan our honeymoon in South America.</li>
<li>We paid a TaskRabbit to scan all of our wedding cards for posterity.</li>
<li>Currently, we have a virtual assistant from <a href="http://odesk.com">oDesk</a> who helps us with the ocasional life task.  She has proofread documents and called gyms around San Francisco, looking for ones with good pools.</li>
<li>We&rsquo;ve given friends custom, hand painted watercolor birthday cards from <a href="http://fiverr.com">Fiverr</a> and custom wedding presents from <a href="http://etsy.com">Etsy</a>.  (Which, one couple swears, is their favorite wedding present!)  We designed the art (roughly) and then it was made with skill by the artists.</li>
<li>When I have outstanding questions, I turn to Aardvark, Yahoo! Answers, and other outsourced question answering services.  I would gladly pay for a better one.</li>
<li>A carpenter on Craigslist designed and built a custom, adjustable standing desk for me.  (Arguably outsourcing, arguably not.)</li>
</ul>


<h3>(Micro-)business Things</h3>

<ul>
<li>I&rsquo;ve hired artists and logo designers on Fiverr.  (I actually bought some excellent artwork and had an ongoing business relationship with an artist who I found for $5 on Fiverr.)</li>
<li>Workers on oDesk and TaskRabbit have helped me brainstorm domain names.</li>
<li>I&rsquo;ve paid users on Mechanical Turk and later on oDesk to label data for me for some Machine Learning research.</li>
<li>I&rsquo;ve brainstormed with a worker from <a href="http://www.coffeeandpower.com">Coffee &amp; Power</a>.</li>
<li>oDeskers have also written software, done graphics, wrote blogs, sent emails, maintained website communities, and researched ideas for me.</li>
</ul>


<h3>Okay, so clearly I&rsquo;ve experimented with this a fair bit.  Here are some of the things that I&rsquo;ve learned:</h3>

<ul>
<li>Outsourcers take maintenance.  It only makes sense to outsource something that would take you more than some threshold amount of time to do yourself.  Repetitive tasks are great candidates.</li>
<li>Be creative- what could you do, if only you had the time?</li>
<li>Outsource things you&rsquo;re bad at, or simply hate doing.</li>
<li>Art is a great thing to outsource.  Finding someone&rsquo;s work that you like on Etsy is fun and addictive and custom gifts make a lasting impression, often costing the same as something far more mundane.</li>
<li>If you&rsquo;re trying to get art off of Fiverr, I recommend contacting 5-10 different providers, having them all do the work for $5 each, and then continuing to work with your favorite.  This same strategy, of redundant hiring and then consolidating, works well across many lifesourcing and outsourcing domains.</li>
<li>You should think about hiring people on oDesk in the same way as you would any other interview process.  Ask to see work, look at portfolios, and, ideally, provide interview challenges that directly map to the work they will be doing for you.  In my case, when I hired someone to maintain one of my websites, my interview questions revolved around writing example emails and deciding which links were worth posting.  When I hired people to classify a dataset, I gave them access to the real classification application and had them do a sample set.  If they did well, I hired them.</li>
<li>If you&rsquo;re going to go through the trouble of interviewing and hiring on oDesk, I strongly recommend codifying your interview and training instructions as reusable documents.  When your current worker(s) inevitably leave or flake out, you can hire and train the next set more quickly.  You can also hire more than one at a time for added redundancy.</li>
<li>There are a shockingly large number of people on this planet who speak (nearly) perfect English, have sharp wits, and are looking for work.  If you have tasks that you can pay them a fair wage to solve, you&rsquo;re helping everyone.  And remember, a fair wage in the Philippines (where many people speak English perfectly), is significantly less than in the US.  Do your cost of living research and pay fairly and generously!</li>
<li>Accountability and incentives are important.  I left Mechanical Turk and instead interviewed and hired individuals off oDesk for data labeling tasks because I received better quality and had more consistency over who I was working with.</li>
</ul>


<p>Are you a lifesourcer or a micro-outsourcer?  What have you learned?</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Machine Learning Project Ideas]]></title>
    <link href="http://blog.andrewcantino.com/blog/2012/04/22/machine-learning-project-ideas/"/>
    <updated>2012-04-22T15:56:00-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2012/04/22/machine-learning-project-ideas</id>
    <content type="html"><![CDATA[<p><a href="http://twitter.com/#!/ryanstout">Ryan Stout</a> and I are giving a <a href="http://railsconf2012.com/sessions/14">talk</a> at RailsConf about Machine Learning tomorrow.  To go along with the talk, here is a list of project ideas to get your creative juices flowing:</p>

<ul>
<li>A robust email and mailing address typo corrector for web forms.</li>
<li>A Rickroll detecting browser plugin- it warns you before you follow a link that will likely result in Rickrolling.  (Rickroll Protection As A Service?)</li>
<li>A per-user clicktrail analyzer that predicts which links a user is most likely to follow, given their history.  Use this to highlight or promote high-likelihood links.</li>
<li>A user info and usage pattern analyzer that classifies users by likelihood of upgrading to a premium plan.</li>
<li>A RubyGem for classifying user generated content into appropriate, inappropriate, spam, NSFW, etc.</li>
<li>Along the same lines: a nudity detector for uploaded images.</li>
<li>A RubyGem for code optimization based on the current backtrace, possibly using reinforcement learning.  For example:</li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'>  <span class="c1"># This probabilistically selects a choice based on the</span>
</span><span class='line'>  <span class="c1"># current backtrace and the history of reinforcement signals seen.</span>
</span><span class='line'>  <span class="n">optimize</span> <span class="k">do</span>
</span><span class='line'>    <span class="n">choice</span> <span class="k">do</span>
</span><span class='line'>      <span class="c1"># some code path that ultimately triggers a &quot;reward&quot; or &quot;punishment&quot; signal</span>
</span><span class='line'>    <span class="k">end</span>
</span><span class='line'>    <span class="n">choice</span> <span class="k">do</span>
</span><span class='line'>      <span class="c1"># some other code path</span>
</span><span class='line'>    <span class="k">end</span>
</span><span class='line'>  <span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>


<ul>
<li>A story karma predictor that estimates the final score on Hacker News of any article, based on textual content and the poster&rsquo;s info.</li>
<li>A system that classifies support requests by their estimated severity.</li>
<li>Make things easier for your users:

<ul>
<li>given them default settings selected by users similar to themselves</li>
<li>default to pages they use often; expand modules they interact with frequently</li>
</ul>
</li>
</ul>


<p>Once you know what&rsquo;s possible, it&rsquo;s hard to find a project that <em>wouldn&rsquo;t</em> benefit from some machine learning.</p>

<p>Have other ideas?  Want to discuss these?  Post them in the comments and follow <a href="http://twitter.com/tectonic">@tectonic</a> for updates.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Fixing the Chrome Background Refresh Bug]]></title>
    <link href="http://blog.andrewcantino.com/blog/2012/02/15/fixing-the-chrome-background-refresh-bug/"/>
    <updated>2012-02-15T11:55:00-08:00</updated>
    <id>http://blog.andrewcantino.com/blog/2012/02/15/fixing-the-chrome-background-refresh-bug</id>
    <content type="html"><![CDATA[<p>There is a <a href="http://code.google.com/p/chromium/issues/detail?id=111218#makechanges">bug in the current version of Chromium</a> (hence Google Chrome) that sometimes fails to redraw CSS background images when they&rsquo;re hidden and then re-shown.  This issue appeared on <a href="http://mavenlink.com/tour">Mavenlink&rsquo;s Tour</a> page.  Thomas Fuchs <a href="http://mir.aculo.us/2011/12/07/the-case-of-the-disappearing-element/">discusses some possible solutions</a>, but none of those worked for us.  Here is our ugly solution:</p>

<div><script src='https://gist.github.com/1838523.js'></script>
<noscript><pre><code>function refreshBackgrounds(selector) {
  // Chrome shim to fix http://groups.google.com/a/chromium.org/group/chromium-bugs/browse_thread/thread/1b6a86d6d4cb8b04/739e937fa945a921
  // Remove this once Chrome fixes its bug.
  if (/chrome/.test(navigator.userAgent.toLowerCase())) {
    $(selector).each(function() {
      var $this = $(this);
      if ($this.css(&quot;background-image&quot;)) {
        var oldBackgroundImage = $this.css(&quot;background-image&quot;);
        setTimeout(function() {
          $this.css(&quot;background-image&quot;, oldBackgroundImage);
        }, 1);
      }
    });
  }
}

// You&#39;ll need to call this every time the event occurs that exposes the bug, such as changing tab divs.
refreshBackgrounds(&quot;.something-with-a-background-image&quot;);
refreshBackgrounds(&quot;*&quot;); // but it&#39;ll be slow!</code></pre></noscript></div>


<p>Please let me know if you find something better!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Hacking Google for Fun and Profit]]></title>
    <link href="http://blog.andrewcantino.com/blog/2011/12/14/hacking-google-for-fun-and-profit/"/>
    <updated>2011-12-14T20:37:00-08:00</updated>
    <id>http://blog.andrewcantino.com/blog/2011/12/14/hacking-google-for-fun-and-profit</id>
    <content type="html"><![CDATA[<p>At the end of last year, Google announced their <a href="http://googleonlinesecurity.blogspot.com/2010/11/rewarding-web-application-security.html">Vulnerability Reward Program</a> which rewards security researchers for reported security and privacy holes in Google properties.  This sounded like an interesting challenge, and I set out to find security holes.  I found three, got paid, and am now in the <a href="http://www.google.com/about/corporate/company/halloffame.html">Google Security Hall of Fame</a>. All in all, a rewarding experience.</p>

<p>Below I describe the three security holes that I found.</p>

<h2>Determining if a user has emailed another user</h2>

<p>In my opinion, this is the most subtle, but also the most disturbing, of the three bugs.  As with the other bugs that I found, this was an example of <a href="http://en.wikipedia.org/wiki/Cross-site_request_forgery">Cross Site Request Forgery</a>&ndash; the practice of convincing a user&rsquo;s browser to make a request on their behalf to a remote server.  This type of attack generally only works when the user is logged in to the remote service.  In this case, if a user is already logged into Gmail (and they usually are), a malicious website could make a series of requests for Gmail profile images and, based on the return codes, determine whether or not the visitor had communicated with another Gmail user.  This worked because Gmail, as a well-intentioned privacy measure, would only show profile images to a viewer if they had had mutual contact.  Here is some example code that worked at the time:</p>

<figure class='code'><figcaption><span>checkUsername</span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">checkUsername</span><span class="p">(</span><span class="nx">username</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">image</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Image</span><span class="p">();</span>
</span><span class='line'>    <span class="nx">image</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>          <span class="nx">callback</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
</span><span class='line'>    <span class="p">};</span>
</span><span class='line'>    <span class="nx">image</span><span class="p">.</span><span class="nx">onerror</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>      <span class="nx">callback</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span><span class='line'>    <span class="p">};</span>
</span><span class='line'>    <span class="nx">image</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="s2">&quot;https://mail.google.com/mail/photos/&quot;</span> <span class="o">+</span> <span class="nx">username</span> <span class="o">+</span> <span class="s2">&quot;%40gmail.com?1&amp;rp=1&amp;pld=1&amp;r=&quot;</span> <span class="o">+</span> <span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">()).</span><span class="nx">getTime</span><span class="p">();</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">checkUsername</span><span class="p">(</span><span class="s2">&quot;fbi-reports&quot;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">hasEmailed</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">alert</span><span class="p">(</span><span class="s2">&quot;The current visitor &quot;</span> <span class="o">+</span> <span class="p">(</span><span class="nx">hasEmailed</span> <span class="o">?</span> <span class="s2">&quot;has&quot;</span> <span class="o">:</span> <span class="s2">&quot;has not&quot;</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&quot; emailed the FBI.&quot;</span><span class="p">);</span>
</span><span class='line'><span class="p">});</span>
</span><span class='line'>
</span><span class='line'><span class="nx">checkUsername</span><span class="p">(</span><span class="s2">&quot;wikileaks&quot;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">hasEmailed</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">alert</span><span class="p">(</span><span class="s2">&quot;The current visitor &quot;</span> <span class="o">+</span> <span class="p">(</span><span class="nx">hasEmailed</span> <span class="o">?</span> <span class="s2">&quot;has&quot;</span> <span class="o">:</span> <span class="s2">&quot;has not&quot;</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&quot; emailed WikiLeaks.&quot;</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p></p>

<p>It should be clear why this is a serious privacy concern.  If you suspected someone of being a whistleblower, for example, you could make a page that probed a bunch of revealing email addresses and checked to see if any had been contacted.  Luckily, Google reports that they have now fixed this bug.  Cross Site Request Forgery attacks can usually be prevented by adding a <a href="http://en.wikipedia.org/wiki/Cross-site_request_forgery#Other_approaches_to_CSRF">CSRF</a> token (a unique and user-specific token) to every request.</p>

<h2>Identification of a user&rsquo;s Gmail address</h2>

<p>This bug would have allowed a malicious website to determine your Google username if you were simultaneously logged into your Google account and typed anything into a seemingly innocuous web form.  One of the fields in the form would actually be an iframe pointing to a public Google Document.  When the user typed into the field, they would really be entering text into the Google Document, and what appeared to be their cursor in the field would actually be the Google Document insertion point.  When a user typed into the field, the attacker could determine their username (and hence email address) by observing the publicly-displayed list of current document editors.</p>

<p>Again, this is a type of Cross Site Request Forgery, specifically known as <a href="http://en.wikipedia.org/wiki/Clickjacking">Clickjacking</a>, which can be especially hard to prevent.  There are many types of Clickjacking, almost all of which use iframes.  One approach, which I used here, is to artfully display content from a target site in such a way as to look like it&rsquo;s part of the current page. Another approach is to hide the iframe invisibly under the user&rsquo;s cursor, moving it as the cursor moves, and causing the user to click on the other site without realizing it.</p>

<p>Google correctly used the <a href="http://msdn.microsoft.com/en-us/library/dd565647.aspx">X-XSS-Protection</a> and <a href="https://developer.mozilla.org/en/The_X-FRAME-OPTIONS_response_header">X-Frame-Options</a> headers, but some browsers do not honor these.  The solution to this one is tricky, but it is generally to use <a href="http://en.wikipedia.org/wiki/Framekiller">frame busting</a>, to provide appropriate headers, to use CSRF tokens, and to not expose any user information without a direct user interaction.</p>

<h2>Deletion of all future email</h2>

<p>The third bug that I found was a fairly severe security hole that affected a portion of Gmail users.  Due to a missing CSRF token during the first step of the filter creation flow in the HTML-only version of Gmail, a malicious site could trick visitors into creating a Gmail filter that would delete all future received email.  This worked in the current (at the time) version of Firefox, but not in Chrome or Safari due to their correct handling of the x-frame-options header.  I didn&rsquo;t test it in IE.</p>

<p>This security hole was exploitable via a combination of a classic Cross Site Request Forgery with a Clickjacking attack.  First, I discovered that it was possible to submit the first part of the filter creation flow in an iframe using JavaScript because Google had forgotten to include a unique CSRF token in the form.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;form</span> <span class="na">id=</span><span class="s">&#39;form&#39;</span> <span class="na">method=</span><span class="s">&#39;POST&#39;</span> <span class="na">target=</span><span class="s">&#39;iframe&#39;</span> <span class="na">action=</span><span class="s">&#39;https://mail.google.com/mail/h/ignored/?v=prf&#39;</span> <span class="na">enctype=</span><span class="s">&#39;multipart/form-data&#39;</span><span class="nt">&gt;</span>
</span><span class='line'>    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">hidden</span> <span class="na">name=</span><span class="s">&#39;cf1_hasnot&#39;</span> <span class="na">value=</span><span class="s">&#39;adfkjhsdf&#39;</span><span class="nt">&gt;</span>
</span><span class='line'>    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">hidden</span> <span class="na">name=</span><span class="s">&#39;s&#39;</span> <span class="na">value=</span><span class="s">&#39;z&#39;</span><span class="nt">&gt;</span>
</span><span class='line'>    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">hidden</span> <span class="na">name=</span><span class="s">&#39;cf2_tr&#39;</span> <span class="na">value=</span><span class="s">&#39;true&#39;</span><span class="nt">&gt;</span>
</span><span class='line'>    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">hidden</span> <span class="na">name=</span><span class="s">&#39;cf1_attach&#39;</span> <span class="na">value=</span><span class="s">&#39;false&#39;</span><span class="nt">&gt;</span>
</span><span class='line'>    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">hidden</span> <span class="na">name=</span><span class="s">&#39;nvp_bu_nxsb&#39;</span> <span class="na">value=</span><span class="s">&#39;Next Step&#39;</span><span class="nt">&gt;</span>
</span><span class='line'>    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">&#39;submit&#39;</span> <span class="na">style=</span><span class="s">&#39;display: none&#39;</span><span class="nt">&gt;</span>
</span><span class='line'><span class="nt">&lt;/form&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>I then positioned the iframe such that the &ldquo;Create Filter&rdquo; button on the subsequent page would fill the frame without showing the button border; only the word &ldquo;Create&rdquo; was visible.  A fake button was then shown around the iframe with a style that matched the gmail style such that when the user believed they were submitting a form with a submit button entitled &ldquo;Create,&rdquo; they were really creating a malicious and destructive filter in Gmail.</p>

<p>Google says this has now been fixed.</p>

<h2>Google&rsquo;s Response</h2>

<p>In all three cases, Google responded promptly to my security report and fixed the bug within a reasonable amount of time.  I was given two $500 awards for the three bugs.  Google generously doubled these amounts when I chose to donate them to charity, so the <a href="http://www.athensconservancy.org/">Athens Conservency</a> and the <a href="http://www.buckeyeforestcouncil.org/">Buckeye Forest Council</a>, two of my favorite local charities in Athens, OH, received one thousand dollars each, care of Google.</p>

<p>These were subtle bugs.  They took trial and error to find.  However, in total, I only spent a few spare evenings of my time.  If Google&rsquo;s products- some of the most secure in the world- are susceptible to these sorts of attacks, you can bet many others are as well.  Every programer makes these mistakes sometimes.  Security is too complicated for anyone to get right all of the time.  Check your code!</p>

<h2>Take your security into your own hands&hellip; or, why you should hack Google too!</h2>

<p>Many companies try to silence security bug reporters through legal threats and sometimes even action, driving discoverers of bugs underground and onto the black market where such knowledge can do real harm.  Google has set an admirable example by creating a program that is enlightened, responsive, and well-run, and I hope other companies move in the same direction.</p>

<p>I had a great time using <a href="http://jsFiddle.net">jsFiddle</a> to explore and demonstrate bugs.  You can do the same&mdash; check out their <a href="http://googleonlinesecurity.blogspot.com/2010/11/rewarding-web-application-security.html">guidelines</a> and do your part to improve the security of products that you love.</p>

<p>Enjoyed this post?  You should <a href="https://twitter.com/intent/follow?original_referer=http%3A%2F%2Fblog.andrewcantino.com%2Fblog%2F2011%2F12%2F14%2Fhacking-google-for-fun-and-profit%2F&region=follow_link&screen_name=tectonic&source=followbutton&variant=2.0">follow me on Twitter</a>.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[How to Make Your Rails App Tweet the Twitter]]></title>
    <link href="http://blog.andrewcantino.com/blog/2011/05/12/how-to-make-your-rails-app-tweet-the-twitter/"/>
    <updated>2011-05-12T17:35:00-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2011/05/12/how-to-make-your-rails-app-tweet-the-twitter</id>
    <content type="html"><![CDATA[<p>Suppose you want to build a Rails application for tracking popular links, and you want it to post the most popular links to Twitter automatically.  This quick tutorial will show you how to do that using the newest version of the Ruby <a href="https://github.com/jnunemaker/twitter">twitter gem</a>.  A little while ago I added the ability for <a href="http://absurdlycool.com">Freebies Finder</a> to tweet popular freebies.  I recently had to do this for another site and decided that a tutorial was in order.</p>

<h2>Setup the accounts</h2>

<p>We&#8217;ll pretend that our website is called AwesomeLinks.com.  <a href="https://twitter.com/signup" target="_blank">Signup for two Twitter accounts</a>, AwesomeLinks and AwesomeLinksDev.  We need to create a Twitter application through which our website can post to these accounts.  Do this by logging into AwesomeLinks and visiting <a href="https://dev.twitter.com/apps/new"><a href="https://dev.twitter.com/apps/new">https://dev.twitter.com/apps/new</a></a>.  Select &#8216;Client&#8217; as the Application Type, skip the Callback URL, and select Read &amp; Write access.  Twitter will give you a OAuth Consumer key and secret, which you will soon need.</p>

<!--more-->


<h2>Getting your OAuth Token and Secret</h2>

<p>Now you need to authorize your new Twitter Application to post on both of your Twitter accounts.  For this, we use a script:</p>

<script src="https://gist.github.com/969776.js?file=get_token.rb"></script>


<p style="font-size: 0.8em">(If you used the Twitter gem in the past, you may have used <code>authorize_from_access</code> for this, but that no longer works.  We now have to require and use oauth separately.)</p>


<p>Fill in your Twitter Application&#8217;s Consumer key and secret and run the script.  You will be prompted to visit a URL and then to enter the PIN that Twitter provides.  Do this for both of your new Twitter accounts and record the results in <code>config/twitter.yml</code>, like so:</p>

<script src="https://gist.github.com/969776.js?file=twitter.yml"></script>


<h2>Initializing the Twitter gem</h2>

<p>Now, create an initializer in <code>config/initializers/twitter.rb</code> and again include your Twitter App&#8217;s key and secret:</p>

<script src="https://gist.github.com/969776.js?file=twitter_initializer.rb"></script>




<p style="font-size: 0.8em">(If you want to Tweet to multiple accounts, you can do this differently and instead make separate <code>Twitter::Client</code> objects, each with their own OAuth tokens.)</p>


<h2>Tweeting the Twitter</h2>

<p>Finally, it&#8217;s time to augment our Link model so that it can send tweets.  I decided to have it tweet the link&#8217;s description and a shortened version of its URL using the following code:</p>

<script src="https://gist.github.com/969776.js?file=link.rb"></script>


<p>The rest is up to you.  You could write a cronjob to automatically call <code>tweet!</code> on every link, or only on those with enough popularity.  Have fun!</p>
]]></content>
  </entry>
  
</feed>
