Formula Injection in Adonisjs with Basic Auth
Formula Injection in Adonisjs with Basic Auth — how this specific combination creates or exposes the vulnerability
Formula injection occurs when untrusted input is interpreted as a formula or expression by downstream systems such as spreadsheets, reporting tools, or workflow engines. In AdonisJS applications that accept user-controlled data and later export it to formats like CSV or Excel, formula injection becomes a realistic threat. When Basic Authentication is used for endpoint protection, the perception of safety can mask injection risks: authentication guards access to the route but does not sanitize or validate the content of user-supplied data.
In AdonisJS, a common pattern is to accept query parameters or body fields (e.g., a search filter or export column) and pass them into a service that generates a spreadsheet. If the input is not validated and is directly written into a cell that begins with characters such as =, +, -, or @, the receiving application (e.g., Excel or Google Sheets) may execute unintended expressions. For example, a user could supply =HYPERLINK("http://malicious.site", "Click me") as a name parameter, and when the exported file is opened, the formula triggers a network call from the victim’s machine.
Basic Auth in AdonisJS is typically enforced via middleware that checks an Authorization header. This protects the route from unauthenticated users but does not alter how the application processes validated credentials or handles downstream data flows. If the authenticated user’s role includes exporting data that incorporates untrusted inputs, the middleware does not prevent formula injection. In a black-box scan, an endpoint that returns 200 for authenticated requests and accepts free-text parameters should be tested for formula injection by submitting payloads such as =7+1 or =cmd|' /C calc'!A0 and observing whether the response is interpreted as executable content downstream.
The risk is amplified when AdonisJS applications integrate with internal tooling that reuses exported data without additional validation. A developer might assume that authentication and rate limiting are sufficient controls; however, formula injection exploits the trust placed in exported file contents rather than the HTTP entry point. The combination of Basic Auth for access control and insufficient input sanitization creates a scenario where an authenticated user—whether legitimate or compromised—can inject expressions that propagate to other systems, potentially leading to data exfiltration or social engineering via the generated files.
Basic Auth-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on input validation, output encoding, and avoiding direct concatenation of user data into contexts where formulas can be interpreted. Below are concrete, AdonisJS-specific code examples that demonstrate secure handling of parameters when using Basic Auth.
Example 1: Basic Auth middleware with validation
Use AdonisJS middleware to enforce Basic Auth and validate incoming data before it reaches business logic. This example uses the built-in validation layer to reject or sanitize dangerous characters in export-related parameters.
// start/hooks.ts
import { Exception } from '@poppinss/utils'
export const handleValidationErrors = (error) => {
if (error.name === 'ValidationException') {
return error.messages
}
// fallback
return [{ field: 'unknown', message: 'Invalid request' }]
}
// middleware/basic_auth.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { schema } from '@ioc:Adonis/Core/Validator'
export default async function basicAuth(ctx: HttpContextContract, next) {
const authHeader = ctx.request.headers().authorization
if (!authHeader || !authHeader.startsWith('Basic ')) {
ctx.response.status(401).send({ error: 'Unauthorized' })
return
}
// decode and validate credentials (implementation omitted for brevity)
await next()
}
// controllers/export.controller.ts
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
const exportSchema = schema.create({
required: false,
schema: {
filter: schema.string.optional({}, [
schema.normalizeUnicode(),
schema.escape(),
(value, pointer) => {
const forbiddenPrefixes = ['=', '+', '-', '@', '\t', '\n']
for (const prefix of forbiddenPrefixes) {
if (value.startsWith(prefix)) {
throw new Error(`Invalid value at ${pointer}: disallowed prefix`)
}
}
return value
},
]),
},
})
export class ExportController {
public async exportReport({ request, response }: HttpContextContract) {
const payload = request.validate({ schema: exportSchema })
// Safe to use payload.filter in downstream services
const safeFilter = payload.filter || ''
// Generate CSV with proper escaping (implementation-specific)
return response.attachment('report.csv').send(generateCsv(safeFilter))
}
}
Example 2: Output encoding for CSV generation
When generating CSV or Excel-compatible output, ensure that values beginning with formula-indicative characters are escaped or prefixed with a neutral character. Many CSV libraries support quoting; alternatively, prefix with a single quote in contexts where that is acceptable by downstream tools.
function generateCsv(filter: string): Buffer {
const escapeCell = (value: string): string => {
const trimmed = value.trim()
if (/^[=\+\-@\t]/.test(trimmed)) {
// Prefix with a single quote to force literal interpretation in Excel
return `'${trimmed}`
}
return trimmed
}
// Example row construction
const row = [`Report for ${escapeCell(filter)}`, new Date().toISOString()]
return Buffer.from(row.join(',') + '\n')
}
Example 3: Rejecting untrusted input for export parameters
If the export functionality does not need rich text or formulas, reject inputs that match known dangerous patterns rather than attempting to sanitize them. This approach reduces complexity and prevents edge cases.
// validate.ts
import { schema } from '@ioc:Adonis/Core/Validator'
export const safeExportSchema = schema.create({
filter: schema.string.optional({}, [
schema.maxLength(255),
(value) => {
if (/[=+\-@\t\n\r]/.test(value)) {
throw new Error('Formula injection detected')
}
return value
},
]),
})