Time Of Check Time Of Use in Adonisjs
How Time Of Check Time Of Use Manifests in Adonisjs
Time Of Check Time Of Use (TOCTOU) vulnerabilities in Adonisjs applications typically occur when the framework validates a resource's state before performing an operation, but that state changes between the validation and the actual operation. This race condition can lead to serious security breaches, particularly in authorization checks and file operations.
In Adonisjs, a common TOCTOU pattern emerges in resource authorization middleware. Consider a scenario where you validate a user's ownership of a resource before allowing an update:
async update({ params, request, auth }) {
const resource = await Resource.find(params.id)
// TOCTOU vulnerability: state can change between find and update
if (resource.userId !== auth.user.id) {
return response.status(403).json({ error: 'Unauthorized' })
}
// The resource could be modified or deleted by another process
// between the authorization check and the update operation
resource.merge(request.post())
await resource.save()
return resource
}This pattern is particularly dangerous in Adonisjs because the framework's ORM operations are not atomic by default. Another process could delete or modify the resource between the authorization check and the save operation, potentially allowing unauthorized access or data corruption.
File upload operations in Adonisjs present another significant TOCTOU surface. When handling file uploads, the framework validates the file's properties before processing, but the file system state can change:
async upload({ request, response }) {
const upload = request.file('document', {
size: '2mb',
extnames: ['pdf', 'docx']
})
// TOCTOU vulnerability: file validation happens before processing
const file = await upload.move(Application.tmpPath('uploads'))
// The file could be replaced or modified between validation and move
if (!file.moved()) {
return response.badRequest(file.error())
}
// Process the file - but it might not be the same file that was validated
await processDocument(file.path)
return response.created()
}The Adonisjs Lucid ORM's transaction handling can also introduce TOCTOU vulnerabilities if not used correctly. When performing operations that span multiple database queries, developers might validate data in one transaction and then perform operations in another, creating a window for state changes:
async transferFunds({ request, response }) {
const { fromId, toId, amount } = request.post()
// TOCTOU vulnerability: separate transactions
const fromAccount = await Account.find(fromId)
if (fromAccount.balance < amount) {
return response.status(400).json({ error: 'Insufficient funds' })
}
// Another process could modify fromAccount.balance here
await Database.transaction(async (trx) => {
await fromAccount.merge({ balance: fromAccount.balance - amount }).save(trx)
await Account.merge({ balance: fromAccount.balance + amount }).save(trx)
})
return response.ok()
}Adonisjs-Specific Detection
Detecting TOCTOU vulnerabilities in Adonisjs applications requires a combination of static analysis and runtime testing. The framework's structure creates specific patterns that security scanners can target.
middleBrick's API security scanner includes specialized detection for TOCTOU vulnerabilities in Adonisjs applications. When scanning Adonisjs endpoints, the scanner looks for several key patterns:
- Authorization checks followed by database operations without transaction wrapping
- File upload handlers that validate before processing
- Multi-step operations that don't use database transactions
- Race condition patterns in middleware chains
- Cache validation followed by direct database access
The scanner specifically targets Adonisjs's controller patterns, looking for the characteristic structure where validation occurs in separate steps from the actual operation. For example, it identifies patterns like:
if (await canAccessResource(user, resource)) {
// TOCTOU window exists here
return await performOperation(resource)
}middleBrick's black-box scanning approach is particularly effective for TOCTOU detection because it can simulate concurrent requests to expose race conditions. The scanner sends multiple parallel requests to the same endpoint with slight variations in timing, looking for inconsistent responses that indicate a TOCTOU vulnerability.
For Adonisjs applications, the scanner also examines the framework's built-in middleware chain, which can introduce TOCTOU windows. The authentication and authorization middleware execute before the controller logic, creating a gap where resource state can change:
// Middleware chain creates TOCTOU window
async handle({ auth, params }, next) {
const user = await auth.getUser()
const resource = await Resource.find(params.id)
if (user.id !== resource.userId) {
return response.status(403).json({ error: 'Unauthorized' })
}
// State can change before next() is called
await next()
}The scanner also checks for proper use of Adonisjs's transaction API, which is the primary mitigation for TOCTOU vulnerabilities. It flags operations that should be wrapped in transactions but aren't, particularly in financial or authorization-sensitive code paths.
Adonisjs-Specific Remediation
Remediating TOCTOU vulnerabilities in Adonisjs requires understanding the framework's transaction model and applying atomic operations. The primary defense is wrapping related operations in database transactions to ensure consistency.
For authorization checks, Adonisjs provides several patterns to eliminate TOCTOU windows. The most robust approach is using database-level constraints combined with transaction wrapping:
async update({ params, request, auth }) {
const resource = await Resource.query()
.where('id', params.id)
.where('user_id', auth.user.id)
.forUpdate() // Lock the row for update
.first()
if (!resource) {
return response.status(404).json({ error: 'Resource not found or unauthorized' })
}
// All operations in a single transaction
await Database.transaction(async (trx) => {
resource.merge(request.post())
await resource.save(trx)
})
return resource
}This pattern eliminates the TOCTOU window by combining the authorization check and the update operation into a single atomic transaction. The forUpdate() lock ensures that no other process can modify the resource while the transaction is in progress.
For file operations, Adonisjs's file handling can be made atomic by using temporary files and atomic rename operations:
async upload({ request, response }) {
const upload = request.file('document', {
size: '2mb',
extnames: ['pdf', 'docx']
})
const tmpPath = Application.tmpPath('uploads', `${Date.now()}-${upload.clientName}`)
// Process file in a single atomic operation
await Database.transaction(async () => {
const file = await upload.move(tmpPath)
if (!file.moved()) {
throw new Error(file.error())
}
// Process the file immediately
await processDocument(file.path)
// Move to final location atomically
const finalPath = Application.publicPath('documents', upload.clientName)
await fs.rename(tmpPath, finalPath)
})
return response.created()
}Adonisjs's Lucid ORM provides optimistic locking through version columns, which can prevent TOCTOU vulnerabilities in concurrent update scenarios:
class Resource extends BaseModel {
static get traits() {
return [
'Adonis/Lucid/SoftDelete',
'@provider:Adonis/Lucid/OptimisticLocking'
]
}
}
async update({ params, request, response }) {
const resource = await Resource.find(params.id)
try {
await resource.merge(request.post()).save({
reload: true,
silent: false
})
} catch (error) {
if (error.code === 'CONFLICT') {
return response.status(409).json({ error: 'Resource was modified by another process' })
}
throw error
}
return resource
}For API endpoints that perform multiple related operations, Adonisjs's transaction API should be used consistently:
async transferFunds({ request, response }) {
const { fromId, toId, amount } = request.post()
await Database.transaction(async (trx) => {
const fromAccount = await Account.query()
.where('id', fromId)
.forUpdate()
.first(trx)
if (fromAccount.balance < amount) {
throw new Error('Insufficient funds')
}
fromAccount.balance -= amount
await fromAccount.save(trx)
const toAccount = await Account.find(toId, trx)
toAccount.balance += amount
await toAccount.save(trx)
})
return response.ok()
}This pattern ensures that both the balance check and the updates happen atomically, eliminating any TOCTOU window where account balances could change between operations.
Frequently Asked Questions
How does middleBrick specifically detect TOCTOU vulnerabilities in Adonisjs applications?
What's the most effective way to prevent TOCTOU attacks in Adonisjs file upload handlers?
Database.transaction() block. Use forUpdate() locks when querying related database records, and consider temporary files with atomic rename operations. Additionally, implement optimistic locking using Adonisjs's @provider:Adonis/Lucid/OptimisticLocking trait to detect when files are modified by concurrent processes.