This page documents the public runtime API exported by @owasp-webshield/core and @owasp-webshield/react.
import React from "react";
import {
ACLManager,
AuthManager,
CSRFTokenManager,
HTTPClient,
RBACManager,
SSRFGuard,
TokenManager
} from "@owasp-webshield/core";
import {
ACLProvider,
AuthGate,
AuthProvider,
PermissionGate,
RBACProvider,
SecurityAlert,
SecurityProvider,
useSafeFetcher,
useSecureHttpClient
} from "@owasp-webshield/react";
const tokenManager = new TokenManager({
onRefresh: async (refreshToken) => ({
accessToken: `rotated-${refreshToken}`,
refreshToken,
expiresAt: Date.now() + 60_000
})
});
const authManager = new AuthManager({ tokenManager });
authManager.setSession({ userId: "u1", roles: ["editor"] });
const aclManager = new ACLManager();
const rbacManager = new RBACManager();
rbacManager.defineRole("editor", ["read:articles", "update:articles"]);
aclManager.setPolicy("articles", "delete", "deny");
function SecureArticleList() {
const client = useSecureHttpClient({
baseUrl: "https://api.example.com",
tokenProvider: () => tokenManager.getAccessToken()
});
const safeFetcher = useSafeFetcher({ allowProtocols: ["https:"] });
async function loadArticles() {
const response = await client.request("/articles", { method: "GET" });
await safeFetcher.fetch("https://cdn.example.com/articles.json");
return response.data;
}
return <button onClick={loadArticles}>Load articles</button>;
}
export function App() {
return (
<SecurityProvider logger={logger} events={events}>
<AuthProvider authManager={authManager}>
<ACLProvider aclManager={aclManager}>
<RBACProvider rbacManager={rbacManager}>
<AuthGate fallback={<SecurityAlert level="warn" message="Please sign in" />}>
<PermissionGate
action="read"
resource="articles"
fallback={<SecurityAlert level="error" message="Access denied" />}
>
<SecureArticleList />
</PermissionGate>
</AuthGate>
</RBACProvider>
</ACLProvider>
</AuthProvider>
</SecurityProvider>
);
}
import {
ACLManager,
ACCESS_CONTROL_TYPES,
PermissionChecker,
RBACManager
} from "@owasp-webshield/core";
const rbac = new RBACManager();
rbac.defineRole("viewer", ["read:reports"]);
rbac.defineRole("analyst", ["export:reports"], ["viewer"]);
rbac.defineRole("support", ["read:*"]);
const acl = new ACLManager();
acl.setPolicy("reports", "delete", "deny");
acl.setPolicy("*", "read", "allow");
const checker = new PermissionChecker({ rbacManager: rbac, aclManager: acl });
checker.check({ role: "analyst", action: "read", resource: "reports" });
checker.check({ role: "support", action: "read", resource: "tickets" });
console.log(ACCESS_CONTROL_TYPES);
RBACManager resolves inherited permissions and wildcard grants.ACLManager applies direct or wildcard policies with deterministic deny overrides.PermissionChecker combines RBAC and ACL and returns { allowed, reason, metadata }.ACCESS_CONTROL_TYPES is a reserved runtime placeholder for category-local type exports.import {
Argon2Adapter,
CryptoManager,
PBKDF2Adapter,
SecretPolicy,
generateSalt
} from "@owasp-webshield/core";
const salt = generateSalt();
const crypto = new CryptoManager({
kdfAdapter: new PBKDF2Adapter({ iterations: 210000, keyLength: 32, digest: "sha256" })
});
const { key } = crypto.deriveKey("correct-horse-battery-staple", salt);
const encrypted = crypto.encrypt("sensitive payload", key);
const decrypted = crypto.decrypt(encrypted, key);
const argon2 = new Argon2Adapter({
deriveFn: (_password, deriveSalt) => Buffer.concat([deriveSalt, Buffer.alloc(32)]).subarray(0, 32)
});
argon2.deriveKey("password", salt, { memoryCost: 19456 });
SecretPolicy.isEntropySufficient("correct-horse-battery-staple", 60);
SecretPolicy.isRotationWindowExceeded(Date.now() - 86_500_000, 86_400_000);
import {
INJECTION_DEFENSE_TYPES,
InputSanitizer,
InputValidator
} from "@owasp-webshield/core";
const sanitizer = new InputSanitizer("moderate");
const cleanHtml = sanitizer.sanitizeHTML('<a href="javascript:alert(1)" onclick="alert(1)">safe</a>');
const validator = new InputValidator();
const validation = validator.validateSchema(
{ email: "user@example.com", password: "secret-123" },
{
email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
password: { required: true, minLength: 8 }
}
);
validator.validateEmail("user@example.com");
validator.validateUrl("https://example.com/profile");
validator.validateLength("secret-123", { min: 8, max: 64 });
console.log(cleanHtml, validation.valid, INJECTION_DEFENSE_TYPES);
import { DesignChecklist, ThreatModelGuard } from "@owasp-webshield/core";
const guard = new ThreatModelGuard({
transitions: { draft: ["review"], review: ["approved"] },
abuseRules: [
{ id: "mfa", message: "MFA required", check: (context) => context.mfaVerified === true },
{ id: "rate-limit", message: "Too many attempts", check: (context) => context.attempts < 5 }
]
});
guard.validateTransition("draft", "review");
guard.evaluateAbuseCase({ mfaVerified: false, attempts: 7 });
const checklist = new DesignChecklist(["2fa", "audit-log", "csrf"]);
checklist.validate(["2fa", "audit-log"]);
import { HardeningReporter, SecurityConfigManager } from "@owasp-webshield/core";
const configManager = new SecurityConfigManager({
debug: true,
cors: { origin: "*" },
cookies: { secure: false, sameSite: "None" }
});
configManager.validateSchema();
const findings = configManager.detectUnsafeSettings();
const report = new HardeningReporter(configManager).generate();
console.log(findings, report);
import { ComponentPolicy, DependencyRiskScanner } from "@owasp-webshield/core";
const scanner = new DependencyRiskScanner({
scan: async () => [
{ name: "left-pad", severity: "high", currentVersion: "1.0.0", fixedVersion: "1.1.0" }
]
});
const results = await scanner.scan();
const gate = await scanner.passesPolicy("high");
const policy = new ComponentPolicy({
allowlist: ["left-pad", "react"],
denylist: ["unsafe-lib"],
minVersions: { react: "18.3.1" }
});
policy.evaluate({ name: "react", version: "18.3.1" });
console.log(results, gate.pass);
import { AuthManager, AUTH_TYPES, TokenManager } from "@owasp-webshield/core";
const tokenManager = new TokenManager({
onRefresh: async (refreshToken, currentAccess) => ({
accessToken: `${currentAccess}-next`,
refreshToken,
expiresAt: Date.now() + 60_000
})
});
tokenManager.setTokens({
accessToken: "access-1",
refreshToken: "refresh-1",
expiresAt: Date.now() + 30_000
});
const authManager = new AuthManager({ tokenManager });
authManager.setSession({ userId: "u1", roles: ["editor"], metadata: { tenant: "acme" } });
await tokenManager.refreshIfNeeded();
tokenManager.getAccessToken();
authManager.isAuthenticated();
authManager.clearSession();
console.log(AUTH_TYPES);
import { CSRFTokenManager, DATA_INTEGRITY_TYPES, HTTPClient, SSRFGuard } from "@owasp-webshield/core";
const csrf = new CSRFTokenManager();
csrf.rotateToken();
csrf.attach({});
csrf.validate(csrf.getToken());
const client = new HTTPClient({
baseUrl: "https://api.example.com",
csrfManager: csrf,
tokenProvider: async () => "access-token",
outboundRequestPolicy: new SSRFGuard()
});
client.addRequestInterceptor(async (config) => ({
...config,
headers: { ...config.headers, "X-Request-Id": "req-1" }
}));
const response = await client.request("/profile", { method: "GET" });
console.log(response.ok, response.data, DATA_INTEGRITY_TYPES);
HTTPClient accepts a tokenProvider function that may return a string, null, or a promise for either value. The client always awaits it before sending the request.import { EventEmitter, SecurityLogger } from "@owasp-webshield/core";
const events = new EventEmitter();
const logger = new SecurityLogger({
sink: (entry) => {
console.log(entry.level, entry.event, entry.details);
}
});
const unsubscribe = events.on("auth:changed", (payload) => {
logger.info("auth.changed", payload);
});
events.emit("auth:changed", {
userId: "u1",
authorization: "Bearer abc",
password: "secret"
});
logger.warn("security.warning", { token: "abc", keep: "value" });
logger.error("security.error", { cookie: "session=1" });
unsubscribe();
import { SSRFGuard, SafeFetcher } from "@owasp-webshield/core";
const guard = new SSRFGuard({ allowProtocols: ["https:"], maxRedirectHops: 2 });
guard.validateUrl("https://api.example.com/users");
guard.validateRedirectChain(["https://a.example.com", "https://b.example.com"]);
const safeFetcher = new SafeFetcher({
guard,
fetchImpl: fetch
});
await safeFetcher.fetch("https://api.example.com/users", { method: "GET" });
Builds and wires TokenManager, AuthManager, RBACManager, ACLManager, EventEmitter, and SecurityLogger from one declarative config, instead of constructing and threading each manager by hand. Pairs with OwlProvider in the React adapter.
import { createOwlClient } from "@owasp-webshield/core";
const owl = createOwlClient({
roles: {
viewer: { permissions: ["read:articles"] },
editor: { permissions: ["update:articles"], inherits: ["viewer"] }
},
acl: [{ resource: "articles", action: "delete", effect: "deny" }],
token: { onRefresh: async (refreshToken) => ({ accessToken: `rotated-${refreshToken}`, refreshToken, expiresAt: Date.now() + 60_000 }) },
logger: { sink: (entry) => console.log(entry) }
});
// owl = { tokenManager, authManager, rbacManager, aclManager, events, logger }
owl.authManager.setSession({ userId: "u1", roles: ["editor"] });
Every returned manager is the same real class you’d get by constructing it directly — anything not covered by this config shape (interceptors, a custom TokenManager storage adapter, etc.) can still be set via its normal API on the returned instance. createOwlClient only covers A01/A07/A09; CSRFTokenManager/HTTPClient/SSRFGuard and the rest are still constructed directly since their config (base URLs, fetch implementations) is too app-specific to generalize.
import { SecurityError, SecurityErrorCode } from "@owasp-webshield/core";
throw new SecurityError(SecurityErrorCode.ACCESS_DENIED, "Report access denied", {
action: "read",
resource: "reports"
});
import React from "react";
import {
ACLProvider,
AuthContext,
AuthGate,
AuthProvider,
PermissionGate,
RBACProvider,
SecurityProvider,
useAuth,
useAuthToken
} from "@owasp-webshield/react";
function SessionSummary() {
const { session, isAuthenticated } = useAuth();
const accessToken = useAuthToken();
const authContext = React.useContext(AuthContext);
return (
<pre>
{JSON.stringify({
isAuthenticated,
userId: session?.userId,
tokenPreview: accessToken?.slice(0, 8),
sameContext: authContext.session?.userId === session?.userId
})}
</pre>
);
}
export function AuthTree({ authManager, aclManager, rbacManager, logger, events }) {
return (
<SecurityProvider logger={logger} events={events}>
<AuthProvider authManager={authManager}>
<ACLProvider aclManager={aclManager}>
<RBACProvider rbacManager={rbacManager}>
<AuthGate fallback={<div>Please sign in</div>}>
<PermissionGate action="read" resource="reports" fallback={<div>Denied</div>}>
<SessionSummary />
</PermissionGate>
</AuthGate>
</RBACProvider>
</ACLProvider>
</AuthProvider>
</SecurityProvider>
);
}
useAuthToken() updates when the underlying TokenManager emits token:changed, token:cleared, or token:rotated.AuthProvider also schedules an auth-state recheck at expiresAt, so AuthGate falls back automatically once the token expires.OwlProvider composes the four providers above into one component — equivalent to the nested tree in AuthTree above, just less of it:
import { AuthGate, OwlProvider, PermissionGate } from "@owasp-webshield/react";
import { owl } from "./security.js"; // owl = createOwlClient({ ... })
export function AuthTree({ children }) {
return (
<OwlProvider client={owl}>
<AuthGate fallback={<div>Please sign in</div>}>
<PermissionGate action="read" resource="reports" fallback={<div>Denied</div>}>
{children}
</PermissionGate>
</AuthGate>
</OwlProvider>
);
}
client accepts anything with authManager/aclManager/rbacManager/logger/events properties (typically the return value of createOwlClient(), but a plain object works too). Individual authManager/aclManager/rbacManager/logger/events props override the same-named property on client.
import React from "react";
import {
ACLContext,
ACLProvider,
PermissionGate,
RBACContext,
RBACProvider,
useACL,
usePermission
} from "@owasp-webshield/react";
function DeleteButton() {
const aclManager = useACL();
const permission = usePermission("delete", "reports");
const aclContext = React.useContext(ACLContext);
const rbacContext = React.useContext(RBACContext);
return (
<button disabled={!permission.allowed} data-acl={Boolean(aclContext)} data-rbac={Boolean(rbacContext)}>
{aclManager.evaluate("reports", "delete").effect}
</button>
);
}
export function AccessControlExample({ aclManager, rbacManager }) {
return (
<ACLProvider aclManager={aclManager}>
<RBACProvider rbacManager={rbacManager}>
<PermissionGate action="delete" resource="reports" fallback={<span>Denied</span>}>
<DeleteButton />
</PermissionGate>
</RBACProvider>
</ACLProvider>
);
}
import React from "react";
import { useCryptoManager } from "@owasp-webshield/react";
export function PasswordPreview() {
const crypto = useCryptoManager();
function handleDerive() {
const { key, salt } = crypto.deriveKey("correct-horse-battery-staple");
console.log(key.length, salt.toString("base64"));
}
return <button onClick={handleDerive}>Derive key</button>;
}
Browser bundle note:
useSecureHttpClientwrapsCSRFTokenManager, which now uses the Web Crypto API and works fine in a browser build.useCryptoManagerwrapsCryptoManager, which is still genuinely Node-only for real encryption (no synchronous browser-portable AES-GCM/PBKDF2 exists) — but both packages now ship a"browser"-conditioned build where it’s a same-shaped stub instead of a build-breaking import, soimport { useCryptoManager } from "@owasp-webshield/react"builds fine in a browser bundle; only calling.encrypt()/.decrypt()/.deriveKey()there throws. See the FAQ for the full explanation.
import React from "react";
import { SanitizedText, useInputSanitizer } from "@owasp-webshield/react";
export function CommentPreview({ rawHtml }) {
const sanitizer = useInputSanitizer("moderate");
const clean = sanitizer.sanitizeHTML(rawHtml);
return (
<div>
<div>{clean}</div>
<SanitizedText profile="strict" html={rawHtml} />
</div>
);
}
import React from "react";
import { useThreatModelGuard } from "@owasp-webshield/react";
export function WorkflowActions() {
const guard = useThreatModelGuard({ transitions: { draft: ["review"], review: ["approved"] } });
const transition = guard.validateTransition("draft", "review");
return <button disabled={!transition.valid}>Submit for review</button>;
}
import React from "react";
import { useHardeningReport } from "@owasp-webshield/react";
export function ConfigDashboard({ config }) {
const findings = useHardeningReport(config);
return (
<ul>
{findings.map((finding) => (
<li key={finding.id}>{finding.recommendation}</li>
))}
</ul>
);
}
import React from "react";
import { useDependencyRiskScanner } from "@owasp-webshield/react";
export function DependencyPanel({ provider }) {
const { loading, results, error, runScan } = useDependencyRiskScanner(provider);
React.useEffect(() => {
runScan().catch(() => {});
}, [runScan]);
if (loading) return <div>Scanning...</div>;
if (error) return <div>{error.message}</div>;
return <pre>{JSON.stringify(results, null, 2)}</pre>;
}
runScan is stable for a stable mounted hook instance and always uses the latest provider supplied to the hook.{ loading, results, error, runScan, scanner }.import React from "react";
import { useSecureHttpClient, withSecurityHeaders } from "@owasp-webshield/react";
export function ProfileLoader({ tokenManager }) {
const client = useSecureHttpClient({
baseUrl: "https://api.example.com",
tokenProvider: async () => tokenManager.getAccessToken()
});
async function loadProfile() {
const response = await client.request(
"/profile",
withSecurityHeaders({
method: "GET",
headers: { "X-Feature": "profile-view" }
})
);
console.log(response.data);
}
return <button onClick={loadProfile}>Load profile</button>;
}
useSecureHttpClient() creates one CSRFTokenManager per hook instance and rotates a token during initialization.withSecurityHeaders() adds OWL defaults and preserves caller-supplied headers.import React from "react";
import {
SecurityAlert,
SecurityContext,
SecurityProvider,
useSecurityMonitoring
} from "@owasp-webshield/react";
function SecurityStatus() {
const { logger, events } = useSecurityMonitoring();
const securityContext = React.useContext(SecurityContext);
React.useEffect(() => {
logger?.info("security.status.rendered", { hasEvents: Boolean(events) });
}, [logger, events]);
return (
<div>
<div>{securityContext.logger ? "monitoring-enabled" : "monitoring-disabled"}</div>
<SecurityAlert level="warn" message="Review recent security events" />
</div>
);
}
export function MonitoringExample({ logger, events }) {
return (
<SecurityProvider logger={logger} events={events}>
<SecurityStatus />
</SecurityProvider>
);
}
useSecurityMonitoring() is safe to call without a provider and returns { logger: null, events: null }.SecurityAlert is a simple presentational component that renders a role=alert container with a data-level attribute.import React from "react";
import { useSafeFetcher } from "@owasp-webshield/react";
export function RemoteConfigLoader() {
const safeFetcher = useSafeFetcher({ allowProtocols: ["https:"] }, fetch);
async function loadConfig() {
const response = await safeFetcher.fetch("https://config.example.com/runtime.json");
console.log(response.ok);
}
return <button onClick={loadConfig}>Load config</button>;
}