Data leaks are the nightmare of every backend developer. You forget one .select('-password') in a new endpoint, and suddenly your user's sensitive data is exposed.
In this deep dive, I'm going to show you how FieldShield solves this problem by hacking into the Mongoose middleware chain to enforce security at the database abstraction level.
The Architecture Problem
In a typical Express/Mongoose app, security is often handled in the Controller layer:
// Controller
const user = await User.findById(req.params.id);
const safeUser = omit(user.toObject(), ['password', 'ssn']); // Manual filtering
res.json(safeUser);
This is prone to human error. If you access the database from a background job, a script, or a different controller, you have to remember to apply the same filter.
Security should be defined where the data is defined: in the Schema.
Enter FieldShield
FieldShield is a native Mongoose plugin that allows you to define per-field access roles directly in your schema definition.
const UserSchema = new Schema({
username: { type: String, shield: { roles: ['public'] } },
email: { type: String, shield: { roles: ['owner', 'admin'] } },
apiKey: { type: String, shield: { roles: ['admin'] } },
password: { type: String, shield: { roles: [] } } // Hidden from everyone
});
Under the Hood: The pre('find') Hook
The magic happens in how we intercept Mongoose queries. When you install FieldShield, we patch the Mongoose Query prototype to accept a context (roles).
Then, we register a global pre hook that inspects the query before it's sent to MongoDB.
// Simplified logic from src/query.ts
schema.pre('find', function() {
const roles = this._shieldRoles; // Passed via .role('admin')
const allowedFields = calculateAllowedFields(modelName, roles);
// We force a projection on the query
this.select(allowedFields);
});
This means the database only returns what you are allowed to see. The sensitive data never even enters your Node.js process memory.
The Challenge: Aggregation Pipelines
Simple queries are easy. But what about Model.aggregate()? Aggregations allow arbitrary stages that can reshape documents, making it hard to track fields.
FieldShield solves this by injecting a $project stage dynamically.
We analyze your pipeline and insert a protection stage right after the initial $match, ensuring that indexes are used efficiently but data is filtered before it flows through the rest of your pipeline (e.g., into $group or $lookup).
// Input
await User.aggregate([
{ $match: { status: 'active' } },
// ... more stages
]).role('public');
// Actual Pipeline Executed
[
{ $match: { status: 'active' } },
{ $project: { username: 1, _id: 1 } }, // Injected by FieldShield
// ... more stages
]
New in v2.2: Recursive Shield Inheritance 🔄
One of the tough technical challenges we just solved in v2.2 was Nested Object and Array Inheritance.
In MongoDB, preferences.theme is a distinct path from preferences. If you hide preferences.notifications, does the user still see the preferences object?
We implemented a recursive parser that synthesizes parent permissions based on their children.
- If any child is visible, the parent field is visible.
- If all children are hidden, the parent is hidden.
- The parent inherits the union of all child roles.
This ensures you don't have to manually effectively duplicate shield configs on parent objects.
Performance Verification 🚀
Because FieldShield uses native MongoDB projections, it's actually faster than fetching the full document and filtering it in JavaScript.
- Network I/O: Reduced (smaller payloads).
- Memory: Reduced (fewer objects created).
- CPU: Reduced (MongoDB handles the filtering in C++).
We Need You! 🫵
FieldShield is fully open source, and we have big plans for v3.0, including:
- 🛡️ Advanced wildcard policies
- 🔍 GraphQL integration
- ⚡ Caching for policy calculation
We are looking for contributors! Whether you're a TypeScript wizard, a Mongoose expert, or just want to write better docs, we'd love your help.
Good First Issues
- Add more unit tests for edge cases
- Improve documentation examples
- Create a benchmark suite
Star the repo and check out the issues:
👉 github.com/kemora13conf/wecon-mongoose-field-shield
Let's build the standard for Mongoose security together.
Top comments (4)
Impressive work, I wish for your project to get the recognition it deserves
Thank you bro.
Finally, I found the best and easiest approach to protection fields
thnx bro 🔥
Feel free to contribute