Time Of Check Time Of Use in Feathersjs
How Time Of Check Time Of Use Manifests in Feathersjs
Time Of Check Time Of Use (TOCTOU) vulnerabilities in Feathersjs applications typically arise from race conditions between authorization checks and data access. This classic security flaw occurs when an application verifies permissions or data state, then acts on that information after a window of time has passed where the underlying state could have changed.
In Feathersjs, TOCTOU most commonly appears in service hooks and authorization middleware. Consider a scenario where a user requests to update a resource: the application first checks if the user has permission to modify that resource, then proceeds to perform the update. If another process modifies the resource's ownership or permissions between these two operations, the original authorization check becomes invalid.
A typical Feathersjs TOCTOU pattern looks like this:
const { authenticate } = require('@feathersjs/authentication').hooks;
const { iff, isProvider } = require('feathers-hooks-common');
module.exports = {
before: {
update: [
authenticate('jwt'),
async context => {
const { user, params, id } = context;
const record = await context.service.get(id);
// TOCTOU vulnerability: check happens here
if (record.ownerId !== user.id) {
throw new Error('Unauthorized');
}
// Update happens later - window for race condition
return context;
}
]
}
}The vulnerability exists because the authorization check and the actual update operation are separated. Between these operations, another request could transfer ownership of the record or delete it entirely.
Another common TOCTOU scenario in Feathersjs involves optimistic concurrency control failures. When multiple users attempt to modify the same resource simultaneously, the last write wins without proper version checking:
// Vulnerable: no version checking
async update(id, data, params) {
return this.patch(id, data, params);
}Feathersjs applications also face TOCTOU issues with real-time events. When using feathers-authentication-hooks with real-time functionality, the authorization state at the time of subscription may differ from the state when events are actually processed.
File-based operations in Feathersjs services present another TOCTOU vector. When a service performs file operations after authorization checks:
async remove(id, params) {
const record = await this.get(id);
// TOCTOU: check happens, but file operation later
if (!params.user.isAdmin) {
throw new Error('Unauthorized');
}
// File deletion happens after check - race condition possible
await fs.unlink(record.filePath);
return this._super(...arguments);
}The gap between checking permissions and executing the file operation creates an opportunity for privilege escalation if the user's role changes between these operations.
Feathersjs-Specific Detection
Detecting TOCTOU vulnerabilities in Feathersjs applications requires both static code analysis and dynamic testing. The most effective approach combines reviewing service hooks for authorization patterns with runtime scanning that simulates concurrent access.
Static analysis should focus on identifying authorization patterns where checks are separated from actions. Look for these specific Feathersjs patterns:
// Pattern to flag: separate check and action
async (context) => {
const record = await context.service.get(context.id);
if (record.ownerId !== context.user.id) {
throw new Error('Unauthorized');
}
return context; // Action happens later
}middleBrick's black-box scanning approach is particularly effective for TOCTOU detection in Feathersjs APIs. The scanner can simulate concurrent requests to expose race conditions that static analysis might miss. When scanning a Feathersjs endpoint, middleBrick:
- Tests authentication state changes between request phases
- Simulates concurrent access to the same resource
- Verifies that authorization checks are performed at the correct execution point
- Checks for proper use of Feathersjs's built-in concurrency controls
- Validates that real-time event subscriptions respect current authorization state
For Feathersjs applications using database adapters, TOCTOU often manifests in how transactions are handled. middleBrick specifically checks for:
// Vulnerable: no transaction wrapping
async update(id, data, params) {
const existing = await this.get(id);
if (existing.ownerId !== params.user.id) {
throw new Error('Unauthorized');
}
return this.patch(id, data, params); // Gap between check and patch
}middleBrick's LLM security scanning also detects TOCTOU vulnerabilities in Feathersjs applications that use AI features. If your Feathersjs service includes AI endpoints, the scanner checks for prompt injection attacks that could manipulate authorization logic:
// AI endpoint vulnerable to TOCTOU via prompt injection
async create(data, params) {
const result = await aiModel.generate(data.prompt);
// TOCTOU: AI response could manipulate authorization logic
if (result.includes('admin_override')) {
params.user.isAdmin = true;
}
// Authorization check now unreliable
return this.create(data, params);
}Dynamic testing with middleBrick involves sending rapid, concurrent requests to identify race conditions. The scanner's 12 security checks include specific tests for TOCTOU scenarios in authentication and authorization flows.
Feathersjs-Specific Remediation
Remediating TOCTOU vulnerabilities in Feathersjs requires architectural changes to eliminate the gap between authorization checks and data operations. The most effective approach is to use atomic operations and database-level constraints wherever possible.
For Feathersjs services, the primary remediation is to combine authorization checks with data operations in a single atomic transaction. Using Feathersjs's transaction support:
const { authenticate } = require('@feathersjs/authentication').hooks;
const { iff, isProvider } = require('feathers-hooks-common');
module.exports = {
before: {
update: [
authenticate('jwt'),
async context => {
const { user, params, id } = context;
// Atomic operation: check and update in single transaction
return context.app.service('users').getModel().transaction(async transaction => {
const record = await context.service.get(id, {
transaction,
params: { ...params, provider: null }
});
if (record.ownerId !== user.id) {
throw new Error('Unauthorized');
}
// Update happens within same transaction
return context.service.patch(id, context.data, {
transaction,
params: { ...params, provider: null }
});
});
}
]
}
}For database operations, use optimistic concurrency control with version numbers. Feathersjs supports this through the version property:
// Version-controlled update to prevent TOCTOU
async update(id, data, params) {
const existing = await this.get(id);
// Check version and ownership atomically
if (existing.version !== data.version) {
throw new Error('Version conflict');
}
if (existing.ownerId !== params.user.id) {
throw new Error('Unauthorized');
}
// Increment version on update
data.version = existing.version + 1;
return this.patch(id, data, params);
}Feathersjs's built-in hooks can help prevent TOCTOU by ensuring authorization checks happen at the correct execution phase. Use the disableMulti option for critical operations:
const { authenticate } = require('@feathersjs/authentication').hooks;
const { iff, isProvider } = require('feathers-hooks-common');
module.exports = {
before: {
update: [
authenticate('jwt'),
iff(
isProvider('external'),
async context => {
const { user, id } = context;
const record = await context.service.get(id);
// Immediate authorization failure if unauthorized
if (record.ownerId !== user.id) {
throw new Error('Unauthorized');
}
// Prevent concurrent modifications
context.dispatch = { ...record, ...context.data };
context.result = record;
return context;
}
)
],
// After hook ensures atomicity
after: {
update: [
async context => {
// Verify state hasn't changed during operation
const updated = await context.service.get(context.id);
if (updated.ownerId !== context.params.user.id) {
throw new Error('State changed during operation');
}
return context;
}
]
}
}
}For file operations in Feathersjs services, use atomic file system operations and validate state after each operation:
const { promisify } = require('util');
const fs = require('fs');
const rename = promisify(fs.rename);
async remove(id, params) {
// Use transaction for both DB and file operations
return this.getModel().transaction(async transaction => {
const record = await this.get(id, { transaction });
if (!params.user.isAdmin) {
throw new Error('Unauthorized');
}
// Atomic file operation with verification
try {
await rename(record.filePath, '/tmp/trash/' + record.id);
// Verify file was moved successfully
const stats = await promisify(fs.stat)('/tmp/trash/' + record.id);
if (stats.size !== record.fileSize) {
throw new Error('File integrity check failed');
}
} catch (error) {
throw new Error('File operation failed: ' + error.message);
}
return this._super(id, params, { transaction });
});
}middleBrick's GitHub Action can be integrated into your CI/CD pipeline to automatically scan for TOCTOU vulnerabilities before deployment:
name: API Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run middleBrick Scan
run: |
npx middlebrick scan https://api.yourservice.com \
--fail-below B \
--output json > security-report.json
- name: Upload Report
uses: actions/upload-artifact@v2
with:
name: security-report
path: security-report.jsonThis setup ensures TOCTOU vulnerabilities are caught early in development, preventing them from reaching production environments.