It’s probably no secret to you that I’m working on a new project called Inject Detect. As I mentioned last week, as a part of that project I need a way to intercept all MongoDB queries made by a Meteor application.

To figure out how to do this, we’ll need to spend a little time diving into the internals of Meteor to discover how it manages its MongoDB collections and drivers.

From there, we can create a Meteor package that intercepts all queries made against all MongoDB collections in an application.

Hold on tight, things are about to get heavy.

Exploring MongoInternals

One way of accomplishing our query interception goal is to monkey patch all of the query functions we care about, like find, findOne, remove, udpate, and upsert.

To do that, we first need to find out where those functions are defined.

It turns out that the functions we’re looking for are defined on the prototype of the MongoConnection object which is declared in the mongo Meteor package:


MongoConnection.prototype.find = function (collectionName, selector, options) {
  ...
};

When we instantiate a new Mongo.Collection in our application, we’re actually invoking the MongoConnection constructor.

So we want to monkey patch functions on MongoConnection. Unfortunately, the MongoConnection object isn’t exported by the mongo package, so we can’t access it directly from our own code.

How do we get to it?

Thankfully, MongoConnection is eventually assigned to MongoInternals.Connection, and the MongoInternals object is globally exported by the mongo package.

This MongoInternals object will be our entry-point for hooking into MongoDB queries at a very low level.

Monkey Patching MongoInternals

Since we know where to look, let’s get to work monkey patching our query functions.

Assuming we have a package already set up, the first thing we’ll do is import MongoInternals from the mongo Meteor package:


import { MongoInternals } from "meteor/mongo";

Let’s apply our first patch to find. First, we’ll save the original reference to find in a variable called _find:


const _find = MongoInternals.Connection.prototype.find;

Now that we’ve saved off a reference to the original find function, let’s override find on the MongoInternals.Connection prototype:


MongoInternals.Connection.prototype.find = function(collection, selector) {
    console.log(`Querying "${collection}" with ${JSON.stringify(selector)}.`);
    return _find.apply(this, arguments);
};

A diagram of a monkey patch.

We’ve successfully monkey patched the find function to print the collection and selector being queried before passing off the function call to the original find function (_find).

Let’s try it out in the shell:


> import "/imports/queryMonkeyPatcher"; // This is our monkey patching module
> Foo = new Mongo.Collection("foos");
> Foo.find({bar: "123"}).fetch();
Querying "foos" with {"bar": "123"}.
[{_id: "...", bar: "123"}]

As long as we import our code before we instantiate our collections, all calls to find in those collections will be routed through our new find function!

Now that we know our find monkey patch works, we can go ahead and repeat the procedure for the rest of the query functions we’re interested in.

How is This Useful?

This kind of low-level query interception can be thought of as collection hooks on steroids.

Josh Owens uses this kind of low-level hooking to explain every query made by an application with his Mongo Explainer Meteor package.

Similarly, Inject Detect will make use of this query-hooking-foo by collecting information on all queries made by your application and sending it off to be inspected for potential NoSQL Injection attacks.

This kind of low level hooking is an incredibly powerful tool that can accomplish some amazing things if used correctly.