Practical, reusable OWASP Top 10 security controls for JavaScript applications: a framework-agnostic core library, adapters for React, Vue, Next.js and Express, and a framework-neutral server layer for Node.js.
Install the core, plus the adapter for your framework. All OWL packages share one version number, so keep them on the same version.
npm install @owasp-webshield/core
npm install @owasp-webshield/react # React
npm install @owasp-webshield/vue # Vue
npm install @owasp-webshield/next # Next.js
npm install @owasp-webshield/express # Express
npm install @owasp-webshield/node # plain Node.js or another server framework
Requirements
Core, React and Vue: Node.js 20 or later
Node, Express and Next.js adapters: Node.js 20.19+ or 22.12+
React 18+, Vue 3.3+, Next.js 14+, Express 4.18+ or 5
TypeScript declarations are included in every package
The core works anywhere JavaScript runs, with no framework and no runtime dependencies.
import { createOwlClient, InputSanitizer, SafeFetcher, SSRFGuard } from "@owasp-webshield/core";
const owl = createOwlClient({ roles: { editor: { permissions: ["update:article"] } } });
owl.rbacManager.can("editor", "update", "article"); // true
new InputSanitizer("moderate").sanitizeHTML('<b>Hi</b><img src=x onerror=alert(1)>');
// '<b>Hi</b><img src="x" />' (the script handler is gone)
const fetcher = new SafeFetcher({ guard: new SSRFGuard() });
await fetcher.fetch("http://169.254.169.254/latest/meta-data/");
// throws SecurityError SSRF_BLOCKED (cloud metadata address)
OwlProvider wires authentication, roles and security logging from one config object. AuthGate and PermissionGate show content only to users who are allowed to see it.
import { createOwlClient } from "@owasp-webshield/core";
import { AuthGate, OwlProvider, PermissionGate } from "@owasp-webshield/react";
const owl = createOwlClient({ roles: { viewer: { permissions: ["read:reports"] } } });
export function App() {
return (
<OwlProvider client={owl}>
<AuthGate fallback={<div>Please sign in</div>}>
<PermissionGate action="read" resource="reports" fallback={<div>Forbidden</div>}>
<div>Secure content</div>
</PermissionGate>
</AuthGate>
</OwlProvider>
);
}
Hiding content in the browser improves the user experience; the server must still enforce every rule. Use the Express or Next.js adapter for that.
A plugin, composables, guard components, v-safe-html (a sanitizing drop-in replacement for v-html) and a Vue Router guard.
import { createApp } from "vue";
import { createOwlClient } from "@owasp-webshield/core";
import { createOwl, vSafeHtml } from "@owasp-webshield/vue";
const owl = createOwl({
client: createOwlClient({ roles: { viewer: { permissions: ["read:reports"] } } })
});
createApp(App).use(owl).directive("safe-html", vSafeHtml).mount("#app");
// In a template: <div v-safe-html="report.summary"></div>
withOwl() runs authentication, CSRF, permission, validation and SSRF checks for a route handler, and turns any failure into a safe JSON error. There are also helpers for middleware, next.config.js security headers, and sessions in Server Components and Server Actions.
// app/api/articles/route.js
import { withOwl } from "@owasp-webshield/next";
export const POST = withOwl(
async (request, context, { session, body }) => Response.json({ by: session.userId, title: body.title }, { status: 201 }),
{
auth: { verifyToken: (token) => sessionStore.lookup(token) },
csrf: true,
permission: { action: "update", resource: "article", checker: owl },
body: { schema: { title: { required: true, type: "string", maxLength: 120 } }, allowUnknownFields: false }
}
);
// no token → 401 AUTH_REQUIRED · extra field such as "role" → 400 INVALID_INPUT · valid → 201
Middleware for authentication, permissions, CSRF, validation, security headers, SSRF checks and safe error responses. It works with Express 4 and 5.
import express from "express";
import { createOwlClient } from "@owasp-webshield/core";
import { csrfProtection, errorHandler, requireAuth, requirePermission, securityHeaders, validate } from "@owasp-webshield/express";
const owl = createOwlClient({ roles: { editor: { permissions: ["write:reports"] } } });
const app = express();
app.use(securityHeaders());
app.use(express.json());
app.use(requireAuth({ verifyToken: (token) => sessionStore.lookup(token) }));
app.use(csrfProtection());
app.post("/reports",
requirePermission("write", "reports", owl),
validate({ title: { required: true, type: "string", maxLength: 120 } }, { allowUnknownFields: false }),
(req, res) => res.status(201).json(req.body)
);
app.use(errorHandler());
// no token → 401 · no CSRF token → 403 CSRF_INVALID · valid request → 201, with security headers