@kamod-ch/otok-security 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kamod
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # @kamod-ch/otok-security
2
+
3
+ Secure defaults and configurable security middleware for [Otok](https://github.com/kamod-ch/otok).
4
+
5
+ Uses Hono's `secure-headers`, `cors`, and `body-limit` with Otok-specific host validation, CSRF, redirect protection, and rate-limit hooks.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @kamod-ch/otok-security hono @kamod-ch/otok
11
+ ```
12
+
13
+ ## Plugin setup
14
+
15
+ ```ts
16
+ import { defineConfig } from "@kamod-ch/otok";
17
+ import security from "@kamod-ch/otok-security";
18
+
19
+ export default defineConfig({
20
+ plugins: [
21
+ security({
22
+ trustedHosts: ["example.com", "www.example.com"],
23
+ trustedOrigins: ["https://example.com"],
24
+ cors: {
25
+ origin: ["https://example.com"],
26
+ credentials: true,
27
+ },
28
+ bodyLimit: 2 * 1024 * 1024,
29
+ rateLimit: createMemoryRateLimitProvider({ limit: 100, windowMs: 60_000 }),
30
+ }),
31
+ ],
32
+ });
33
+ ```
34
+
35
+ ## Middleware order
36
+
37
+ Register `security()` **first** among plugins so headers, CSRF, and body limits apply before auth, i18n, SEO, and SSR:
38
+
39
+ ```ts
40
+ plugins: [security(), observability(), i18n(), auth(), seo()];
41
+ ```
42
+
43
+ Auth CSRF (`@kamod-ch/otok-auth/csrf`) can replace the built-in CSRF by setting `csrf: false` only in non-production with `strict: false`.
44
+
45
+ ## Secure cookies
46
+
47
+ ```ts
48
+ import { secureCookieOptions } from "@kamod-ch/otok-security/cookies";
49
+
50
+ setCookie(c, "pref", value, secureCookieOptions({ maxAge: 3600 }));
51
+ ```
52
+
53
+ Production rejects `secure: false` on cookies.
54
+
55
+ ## Open redirects
56
+
57
+ ```ts
58
+ import { safeRedirectTarget } from "@kamod-ch/otok-security/redirect";
59
+
60
+ return redirect(safeRedirectTarget(nextPath, c));
61
+ ```
62
+
63
+ Query params `redirect`, `returnTo`, `next`, and `url` are validated automatically.
64
+
65
+ ## Rate limiting
66
+
67
+ Implement `RateLimitProvider` for Redis, Cloudflare, or other backends:
68
+
69
+ ```ts
70
+ const rateLimit: RateLimitProvider = {
71
+ async check(ctx) {
72
+ // return { allowed: false, retryAfterSeconds: 30 }
73
+ return { allowed: true, limit: 100, remaining: 99 };
74
+ },
75
+ };
76
+ ```
77
+
78
+ ## Defaults
79
+
80
+ | Feature | Development | Production |
81
+ | -------------- | -------------------- | ------------- |
82
+ | Secure headers | on | on |
83
+ | CSP | baseline restrictive | on (required) |
84
+ | CSRF | on when enabled | on (required) |
85
+ | Body limit | 1 MB | 1 MB |
86
+ | Secure cookies | off unless HTTPS | secure |
87
+
88
+ Disabling CSP or CSRF in production throws unless `strict: false`.
89
+
90
+ ## API
91
+
92
+ | Export | Purpose |
93
+ | ------------------------------- | -------------------------- |
94
+ | `security()` | Plugin factory |
95
+ | `configureSecurityApp` | Manual Hono wiring |
96
+ | `createSecurityCsrfMiddleware` | Standalone CSRF |
97
+ | `createOpenRedirectGuard` | Query param redirect guard |
98
+ | `createMemoryRateLimitProvider` | Dev/test rate limiter |
99
+ | `secureCookieOptions` | Safe cookie defaults |
@@ -0,0 +1,9 @@
1
+ import type { Context } from "hono";
2
+ import type { CookieOptions } from "hono/utils/cookie";
3
+ import type { SecureCookieDefaults } from "./types.js";
4
+ export declare function resolveSecureCookieDefaults(options: boolean | SecureCookieDefaults | undefined): SecureCookieDefaults;
5
+ /** Apply secure cookie defaults to Hono setCookie options. */
6
+ export declare function secureCookieOptions(partial?: CookieOptions, defaults?: SecureCookieDefaults): CookieOptions;
7
+ export declare function attachSecureCookieDefaults(c: Context, defaults: SecureCookieDefaults): void;
8
+ export declare function readSecureCookieDefaults(c: Context): SecureCookieDefaults | undefined;
9
+ //# sourceMappingURL=cookies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../src/cookies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAMvD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,oBAAoB,GAAG,SAAS,GAAG,oBAAoB,CAcrH;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,aAAkB,EAAE,QAAQ,CAAC,EAAE,oBAAoB,GAAG,aAAa,CAY/G;AAED,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAE3F;AAED,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,OAAO,GAAG,oBAAoB,GAAG,SAAS,CAErF"}
@@ -0,0 +1,36 @@
1
+ function isProduction() {
2
+ return process.env.NODE_ENV === "production";
3
+ }
4
+ export function resolveSecureCookieDefaults(options) {
5
+ if (options === false) {
6
+ throw new Error("otok-security: secureCookies cannot be disabled in production. Pass explicit SecureCookieDefaults instead.");
7
+ }
8
+ const overrides = typeof options === "object" ? options : {};
9
+ return {
10
+ secure: overrides.secure ?? isProduction(),
11
+ httpOnly: overrides.httpOnly ?? true,
12
+ sameSite: overrides.sameSite ?? "Lax",
13
+ path: overrides.path ?? "/",
14
+ };
15
+ }
16
+ /** Apply secure cookie defaults to Hono setCookie options. */
17
+ export function secureCookieOptions(partial = {}, defaults) {
18
+ const base = defaults ?? resolveSecureCookieDefaults(true);
19
+ if (isProduction() && partial.secure === false) {
20
+ throw new Error("otok-security: setting secure: false on cookies is not allowed in production");
21
+ }
22
+ return {
23
+ path: base.path,
24
+ secure: partial.secure ?? base.secure,
25
+ httpOnly: partial.httpOnly ?? base.httpOnly,
26
+ sameSite: partial.sameSite ?? base.sameSite,
27
+ ...partial,
28
+ };
29
+ }
30
+ export function attachSecureCookieDefaults(c, defaults) {
31
+ c.set("securityCookieDefaults", defaults);
32
+ }
33
+ export function readSecureCookieDefaults(c) {
34
+ return c.get("securityCookieDefaults");
35
+ }
36
+ //# sourceMappingURL=cookies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookies.js","sourceRoot":"","sources":["../src/cookies.ts"],"names":[],"mappings":"AAIA,SAAS,YAAY;IACnB,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,OAAmD;IAC7F,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,4GAA4G,CAC7G,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7D,OAAO;QACL,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,YAAY,EAAE;QAC1C,QAAQ,EAAE,SAAS,CAAC,QAAQ,IAAI,IAAI;QACpC,QAAQ,EAAE,SAAS,CAAC,QAAQ,IAAI,KAAK;QACrC,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,GAAG;KAC5B,CAAC;AACJ,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,mBAAmB,CAAC,UAAyB,EAAE,EAAE,QAA+B;IAC9F,MAAM,IAAI,GAAG,QAAQ,IAAI,2BAA2B,CAAC,IAAI,CAAC,CAAC;IAC3D,IAAI,YAAY,EAAE,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAC;IAClG,CAAC;IACD,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM;QACrC,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ;QAC3C,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ;QAC3C,GAAG,OAAO;KACX,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,CAAU,EAAE,QAA8B;IACnF,CAAC,CAAC,GAAG,CAAC,wBAAwB,EAAE,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,CAAU;IACjD,OAAO,CAAC,CAAC,GAAG,CAAC,wBAAwB,CAAqC,CAAC;AAC7E,CAAC"}
package/dist/csrf.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { type OtokMiddleware } from "@kamod-ch/otok/server";
2
+ import type { CsrfOptions } from "./types.js";
3
+ export declare const CSRF_FIELD = "_csrf";
4
+ export declare function createSecurityCsrfMiddleware(options?: CsrfOptions): OtokMiddleware;
5
+ //# sourceMappingURL=csrf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../src/csrf.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,eAAO,MAAM,UAAU,UAAU,CAAC;AASlC,wBAAgB,4BAA4B,CAAC,OAAO,GAAE,WAAgB,GAAG,cAAc,CAoCtF"}
package/dist/csrf.js ADDED
@@ -0,0 +1,40 @@
1
+ import { defineMiddleware } from "@kamod-ch/otok/server";
2
+ import { getCookie, setCookie } from "hono/cookie";
3
+ import { secureCookieOptions } from "./cookies.js";
4
+ export const CSRF_FIELD = "_csrf";
5
+ const PRODUCTION = () => process.env.NODE_ENV === "production";
6
+ function randomToken() {
7
+ const bytes = new Uint8Array(32);
8
+ crypto.getRandomValues(bytes);
9
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
10
+ }
11
+ export function createSecurityCsrfMiddleware(options = {}) {
12
+ const cookieName = options.cookieName ?? "otok_csrf";
13
+ const secure = options.secure ?? PRODUCTION();
14
+ if (PRODUCTION() && secure === false) {
15
+ throw new Error("otok-security: CSRF cookies must be secure in production");
16
+ }
17
+ return defineMiddleware(async (c, next) => {
18
+ const method = c.req.method.toUpperCase();
19
+ if (method === "GET" || method === "HEAD") {
20
+ if (!getCookie(c, cookieName)) {
21
+ setCookie(c, cookieName, randomToken(), secureCookieOptions({ secure, sameSite: options.sameSite ?? "Lax", httpOnly: false }));
22
+ }
23
+ await next();
24
+ return;
25
+ }
26
+ if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
27
+ const contentType = c.req.header("content-type") ?? "";
28
+ if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
29
+ const cookieToken = getCookie(c, cookieName);
30
+ const formData = await c.req.raw.clone().formData();
31
+ const formToken = formData.get(CSRF_FIELD);
32
+ if (!cookieToken || typeof formToken !== "string" || formToken !== cookieToken) {
33
+ return c.text("CSRF token invalid", 403);
34
+ }
35
+ }
36
+ }
37
+ await next();
38
+ });
39
+ }
40
+ //# sourceMappingURL=csrf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csrf.js","sourceRoot":"","sources":["../src/csrf.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAuB,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGnD,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC;AAClC,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;AAE/D,SAAS,WAAW;IAClB,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IACjC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,UAAuB,EAAE;IACpE,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,WAAW,CAAC;IACrD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,UAAU,EAAE,CAAC;IAC9C,IAAI,UAAU,EAAE,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IAED,OAAO,gBAAgB,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACxC,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAC1C,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YAC1C,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC;gBAC9B,SAAS,CACP,CAAC,EACD,UAAU,EACV,WAAW,EAAE,EACb,mBAAmB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CACtF,CAAC;YACJ,CAAC;YACD,MAAM,IAAI,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACxD,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;YACvD,IAAI,WAAW,CAAC,QAAQ,CAAC,mCAAmC,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC;gBAC7G,MAAM,WAAW,GAAG,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;gBAC7C,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,CAAC;gBACpD,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBAC3C,IAAI,CAAC,WAAW,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,WAAW,EAAE,CAAC;oBAC/E,OAAO,CAAC,CAAC,IAAI,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;gBAC3C,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { secureCookieOptions, resolveSecureCookieDefaults, readSecureCookieDefaults } from "./cookies.js";
2
+ export { CSRF_FIELD, createSecurityCsrfMiddleware } from "./csrf.js";
3
+ export { configureSecurityApp, createOpenRedirectGuard, createRateLimitMiddleware, assertSecureConfiguration, } from "./middleware.js";
4
+ export { default } from "./plugin.js";
5
+ export { safeRedirectTarget, isSafeRedirect } from "./redirect.js";
6
+ export { createMemoryRateLimitProvider } from "./rate-limit.js";
7
+ export type { ContentSecurityPolicyOptionHandler, CorsOptions, CspOptions, CsrfOptions, OpenRedirectOptions, ProxyOptions, RateLimitContext, RateLimitProvider, RateLimitResult, SecureCookieDefaults, SecureHeadersOptions, SecurityPluginOptions, } from "./types.js";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAC1G,OAAO,EAAE,UAAU,EAAE,4BAA4B,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAChE,YAAY,EACV,kCAAkC,EAClC,WAAW,EACX,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { secureCookieOptions, resolveSecureCookieDefaults, readSecureCookieDefaults } from "./cookies.js";
2
+ export { CSRF_FIELD, createSecurityCsrfMiddleware } from "./csrf.js";
3
+ export { configureSecurityApp, createOpenRedirectGuard, createRateLimitMiddleware, assertSecureConfiguration, } from "./middleware.js";
4
+ export { default } from "./plugin.js";
5
+ export { safeRedirectTarget, isSafeRedirect } from "./redirect.js";
6
+ export { createMemoryRateLimitProvider } from "./rate-limit.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAC1G,OAAO,EAAE,UAAU,EAAE,4BAA4B,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { Hono } from "hono";
2
+ import { createSecurityCsrfMiddleware } from "./csrf.js";
3
+ import { createOpenRedirectGuard } from "./redirect.js";
4
+ import { createRateLimitMiddleware } from "./rate-limit.js";
5
+ import type { SecurityPluginOptions } from "./types.js";
6
+ export declare function assertSecureConfiguration(options: SecurityPluginOptions): void;
7
+ /** Apply security middleware stack to a Hono app. Order: rate limit → host → redirect guard → body limit → CORS → CSRF → headers/CSP. */
8
+ export declare function configureSecurityApp(app: Hono, options: SecurityPluginOptions): void;
9
+ export { createOpenRedirectGuard, createRateLimitMiddleware, createSecurityCsrfMiddleware };
10
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAMjC,OAAO,EAAE,4BAA4B,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAc,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAuDpE,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAa9E;AAED,yIAAyI;AACzI,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,qBAAqB,GAAG,IAAI,CA+DpF;AAED,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,4BAA4B,EAAE,CAAC"}
@@ -0,0 +1,120 @@
1
+ import { cors } from "hono/cors";
2
+ import { bodyLimit } from "hono/body-limit";
3
+ import { secureHeaders } from "hono/secure-headers";
4
+ import { defineMiddleware } from "@kamod-ch/otok/server";
5
+ import { attachSecureCookieDefaults, resolveSecureCookieDefaults } from "./cookies.js";
6
+ import { createSecurityCsrfMiddleware } from "./csrf.js";
7
+ import { createOpenRedirectGuard } from "./redirect.js";
8
+ import { createRateLimitMiddleware } from "./rate-limit.js";
9
+ function isProduction() {
10
+ return process.env.NODE_ENV === "production";
11
+ }
12
+ const DEFAULT_CSP = {
13
+ "default-src": "'self'",
14
+ "base-uri": "'self'",
15
+ "font-src": "'self'",
16
+ "form-action": "'self'",
17
+ "frame-ancestors": "'none'",
18
+ "img-src": "'self' data: https:",
19
+ "object-src": "'none'",
20
+ "script-src": "'self'",
21
+ "style-src": "'self' 'unsafe-inline'",
22
+ "upgrade-insecure-requests": "",
23
+ };
24
+ function normalizeHosts(hosts) {
25
+ return (hosts ?? []).map((host) => host.toLowerCase());
26
+ }
27
+ function buildCspDirectives(options) {
28
+ const base = { ...DEFAULT_CSP };
29
+ if (!options || options === true)
30
+ return base;
31
+ for (const [key, value] of Object.entries(options.directives ?? {})) {
32
+ base[key] = Array.isArray(value) ? value.join(" ") : value;
33
+ }
34
+ if (options.reportUri)
35
+ base["report-uri"] = options.reportUri;
36
+ return base;
37
+ }
38
+ function createHostValidationMiddleware(trustedHosts, trustedOrigins) {
39
+ const hosts = normalizeHosts(trustedHosts);
40
+ const origins = new Set(trustedOrigins.map((o) => o.replace(/\/$/, "")));
41
+ return defineMiddleware(async (c, next) => {
42
+ const host = c.req.header("host")?.split(":")[0]?.toLowerCase();
43
+ if (hosts.length > 0 && host && !hosts.includes(host)) {
44
+ return c.text("Invalid Host", 400);
45
+ }
46
+ const method = c.req.method.toUpperCase();
47
+ if (origins.size > 0 && ["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
48
+ const origin = c.req.header("origin");
49
+ if (origin && !origins.has(origin.replace(/\/$/, ""))) {
50
+ return c.text("Invalid Origin", 403);
51
+ }
52
+ }
53
+ await next();
54
+ });
55
+ }
56
+ export function assertSecureConfiguration(options) {
57
+ if (options.strict === false)
58
+ return;
59
+ if (!isProduction())
60
+ return;
61
+ if (options.csp === false) {
62
+ throw new Error("otok-security: CSP cannot be disabled in production (set strict: false to override)");
63
+ }
64
+ if (options.csrf === false) {
65
+ throw new Error("otok-security: CSRF cannot be disabled in production (set strict: false to override)");
66
+ }
67
+ if (options.secureHeaders === false) {
68
+ throw new Error("otok-security: secureHeaders cannot be disabled in production");
69
+ }
70
+ }
71
+ /** Apply security middleware stack to a Hono app. Order: rate limit → host → redirect guard → body limit → CORS → CSRF → headers/CSP. */
72
+ export function configureSecurityApp(app, options) {
73
+ assertSecureConfiguration(options);
74
+ const cookieDefaults = resolveSecureCookieDefaults(options.secureCookies ?? true);
75
+ app.use("*", async (c, next) => {
76
+ attachSecureCookieDefaults(c, cookieDefaults);
77
+ await next();
78
+ });
79
+ if (options.rateLimit) {
80
+ app.use("*", createRateLimitMiddleware(options.rateLimit, options.proxy?.clientIpHeader));
81
+ }
82
+ const trustedHosts = options.trustedHosts ?? (isProduction() ? [] : undefined);
83
+ const trustedOrigins = options.trustedOrigins ?? [];
84
+ if (trustedHosts && trustedHosts.length > 0) {
85
+ app.use("*", createHostValidationMiddleware(trustedHosts, trustedOrigins));
86
+ }
87
+ else if (isProduction() && options.strict !== false) {
88
+ console.warn("otok-security: trustedHosts is empty in production. Set trustedHosts to your public hostnames.");
89
+ }
90
+ if (options.openRedirectGuard !== false) {
91
+ app.use("*", createOpenRedirectGuard(typeof options.openRedirectGuard === "object" ? options.openRedirectGuard : {}));
92
+ }
93
+ if (options.bodyLimit !== false) {
94
+ app.use("*", bodyLimit({ maxSize: typeof options.bodyLimit === "number" ? options.bodyLimit : 1024 * 1024 }));
95
+ }
96
+ if (options.cors) {
97
+ app.use("*", cors({
98
+ origin: options.cors.origin,
99
+ allowMethods: options.cors.allowMethods ?? ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
100
+ allowHeaders: options.cors.allowHeaders,
101
+ exposeHeaders: options.cors.exposeHeaders,
102
+ maxAge: options.cors.maxAge,
103
+ credentials: options.cors.credentials ?? false,
104
+ }));
105
+ }
106
+ const csrfEnabled = options.csrf !== false && (options.csrf === true || isProduction() || typeof options.csrf === "object");
107
+ if (csrfEnabled) {
108
+ app.use("*", createSecurityCsrfMiddleware(typeof options.csrf === "object" ? options.csrf : {}));
109
+ }
110
+ if (options.secureHeaders !== false) {
111
+ const cspEnabled = options.csp !== false;
112
+ const directives = cspEnabled ? buildCspDirectives(typeof options.csp === "object" ? options.csp : {}) : undefined;
113
+ app.use("*", secureHeaders({
114
+ contentSecurityPolicy: directives,
115
+ ...(typeof options.secureHeaders === "object" ? options.secureHeaders.overrides : {}),
116
+ }));
117
+ }
118
+ }
119
+ export { createOpenRedirectGuard, createRateLimitMiddleware, createSecurityCsrfMiddleware };
120
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { configureSecurityApp } from "./middleware.js";
2
+ import type { SecurityPluginOptions } from "./types.js";
3
+ /**
4
+ * Otok security plugin — secure defaults with explicit opt-out.
5
+ *
6
+ * ```ts
7
+ * import security from "@kamod-ch/otok-security";
8
+ *
9
+ * export default defineConfig({
10
+ * plugins: [security({ trustedHosts: ["example.com"], cors: { origin: "https://app.example.com" } })],
11
+ * });
12
+ * ```
13
+ */
14
+ export default function security(options?: SecurityPluginOptions): import("@kamod-ch/otok-config").OtokPlugin<unknown>;
15
+ export { configureSecurityApp };
16
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAexD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAO,GAAE,qBAA0B,uDAMnE;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
package/dist/plugin.js ADDED
@@ -0,0 +1,34 @@
1
+ import { definePlugin } from "@kamod-ch/otok";
2
+ import { configureSecurityApp } from "./middleware.js";
3
+ const securityPluginFactory = definePlugin({
4
+ name: "@kamod-ch/otok-security",
5
+ version: "1.0.0",
6
+ schema: {
7
+ parse(input) {
8
+ if (input != null && typeof input !== "object") {
9
+ throw new Error("security() options must be an object");
10
+ }
11
+ return (input ?? {});
12
+ },
13
+ },
14
+ });
15
+ /**
16
+ * Otok security plugin — secure defaults with explicit opt-out.
17
+ *
18
+ * ```ts
19
+ * import security from "@kamod-ch/otok-security";
20
+ *
21
+ * export default defineConfig({
22
+ * plugins: [security({ trustedHosts: ["example.com"], cors: { origin: "https://app.example.com" } })],
23
+ * });
24
+ * ```
25
+ */
26
+ export default function security(options = {}) {
27
+ const plugin = securityPluginFactory(options);
28
+ plugin.configureApp = ({ app }) => {
29
+ configureSecurityApp(app, options);
30
+ };
31
+ return plugin;
32
+ }
33
+ export { configureSecurityApp };
34
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAGvD,MAAM,qBAAqB,GAAG,YAAY,CAAwB;IAChE,IAAI,EAAE,yBAAyB;IAC/B,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE;QACN,KAAK,CAAC,KAAK;YACT,IAAI,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC/C,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;YAC1D,CAAC;YACD,OAAO,CAAC,KAAK,IAAI,EAAE,CAA0B,CAAC;QAChD,CAAC;KACF;CACF,CAAC,CAAC;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,UAAiC,EAAE;IAClE,MAAM,MAAM,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,CAAC,YAAY,GAAG,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QAChC,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC,CAAC;IACF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { type OtokMiddleware } from "@kamod-ch/otok/server";
2
+ import type { RateLimitProvider } from "./types.js";
3
+ export declare function createRateLimitMiddleware(provider: RateLimitProvider, ipHeader?: string): OtokMiddleware;
4
+ /** In-memory rate limit provider for development and tests. */
5
+ export declare function createMemoryRateLimitProvider(options: {
6
+ limit: number;
7
+ windowMs: number;
8
+ }): RateLimitProvider;
9
+ //# sourceMappingURL=rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,QAAQ,SAAoB,GAAG,cAAc,CAwBnH;AAQD,+DAA+D;AAC/D,wBAAgB,6BAA6B,CAAC,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,iBAAiB,CA6B7G"}
@@ -0,0 +1,65 @@
1
+ import { defineMiddleware } from "@kamod-ch/otok/server";
2
+ export function createRateLimitMiddleware(provider, ipHeader = "x-forwarded-for") {
3
+ return defineMiddleware(async (c, next) => {
4
+ const ip = resolveClientIp(c, ipHeader);
5
+ const result = await provider.check({
6
+ key: ip ?? c.req.header("cf-connecting-ip") ?? "unknown",
7
+ method: c.req.method,
8
+ path: new URL(c.req.url).pathname,
9
+ ip,
10
+ });
11
+ if (!result.allowed) {
12
+ if (result.limit != null)
13
+ c.header("x-ratelimit-limit", String(result.limit));
14
+ if (result.remaining != null)
15
+ c.header("x-ratelimit-remaining", String(result.remaining));
16
+ if (result.resetAt != null)
17
+ c.header("x-ratelimit-reset", String(result.resetAt));
18
+ const retry = result.retryAfterSeconds ?? 60;
19
+ c.header("retry-after", String(retry));
20
+ return c.text("Too Many Requests", 429);
21
+ }
22
+ if (result.limit != null)
23
+ c.header("x-ratelimit-limit", String(result.limit));
24
+ if (result.remaining != null)
25
+ c.header("x-ratelimit-remaining", String(result.remaining));
26
+ await next();
27
+ });
28
+ }
29
+ function resolveClientIp(c, header) {
30
+ const raw = c.req.header(header);
31
+ if (!raw)
32
+ return undefined;
33
+ return raw.split(",")[0]?.trim();
34
+ }
35
+ /** In-memory rate limit provider for development and tests. */
36
+ export function createMemoryRateLimitProvider(options) {
37
+ const buckets = new Map();
38
+ return {
39
+ check(ctx) {
40
+ const now = Date.now();
41
+ const bucket = buckets.get(ctx.key);
42
+ if (!bucket || bucket.resetAt <= now) {
43
+ buckets.set(ctx.key, { count: 1, resetAt: now + options.windowMs });
44
+ return { allowed: true, limit: options.limit, remaining: options.limit - 1, resetAt: now + options.windowMs };
45
+ }
46
+ bucket.count += 1;
47
+ if (bucket.count > options.limit) {
48
+ return {
49
+ allowed: false,
50
+ limit: options.limit,
51
+ remaining: 0,
52
+ resetAt: bucket.resetAt,
53
+ retryAfterSeconds: Math.ceil((bucket.resetAt - now) / 1000),
54
+ };
55
+ }
56
+ return {
57
+ allowed: true,
58
+ limit: options.limit,
59
+ remaining: options.limit - bucket.count,
60
+ resetAt: bucket.resetAt,
61
+ };
62
+ },
63
+ };
64
+ }
65
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { Context } from "hono";
2
+ import { type OtokMiddleware } from "@kamod-ch/otok/server";
3
+ import type { OpenRedirectOptions } from "./types.js";
4
+ declare function isSafeRedirect(target: string, origin: string): boolean;
5
+ export declare function createOpenRedirectGuard(options?: OpenRedirectOptions): OtokMiddleware;
6
+ /** Validate a redirect URL before sending a Location header. */
7
+ export declare function safeRedirectTarget(target: string, c: Context): string;
8
+ export { isSafeRedirect };
9
+ //# sourceMappingURL=redirect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../src/redirect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAItD,iBAAS,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAU/D;AAED,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,mBAAwB,GAAG,cAAc,CAazF;AAED,gEAAgE;AAChE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,GAAG,MAAM,CAMrE;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { defineMiddleware } from "@kamod-ch/otok/server";
2
+ const DEFAULT_PARAMS = ["redirect", "returnTo", "next", "url", "return", "continue"];
3
+ function isSafeRedirect(target, origin) {
4
+ if (!target)
5
+ return false;
6
+ if (target.startsWith("/") && !target.startsWith("//"))
7
+ return true;
8
+ try {
9
+ const parsed = new URL(target, origin);
10
+ const base = new URL(origin);
11
+ return parsed.origin === base.origin;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ export function createOpenRedirectGuard(options = {}) {
18
+ const params = options.params ?? DEFAULT_PARAMS;
19
+ return defineMiddleware(async (c, next) => {
20
+ const url = new URL(c.req.url);
21
+ for (const name of params) {
22
+ const value = url.searchParams.get(name);
23
+ if (value && !isSafeRedirect(value, url.origin)) {
24
+ return c.text("Invalid redirect target", 400);
25
+ }
26
+ }
27
+ await next();
28
+ });
29
+ }
30
+ /** Validate a redirect URL before sending a Location header. */
31
+ export function safeRedirectTarget(target, c) {
32
+ const origin = new URL(c.req.url).origin;
33
+ if (!isSafeRedirect(target, origin)) {
34
+ throw new Error(`otok-security: unsafe redirect target "${target}"`);
35
+ }
36
+ return target;
37
+ }
38
+ export { isSafeRedirect };
39
+ //# sourceMappingURL=redirect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect.js","sourceRoot":"","sources":["../src/redirect.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAuB,MAAM,uBAAuB,CAAC;AAG9E,MAAM,cAAc,GAAG,CAAC,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;AAErF,SAAS,cAAc,CAAC,MAAc,EAAE,MAAc;IACpD,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACpE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACvC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC7B,OAAO,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,UAA+B,EAAE;IACvE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,cAAc,CAAC;IAEhD,OAAO,gBAAgB,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACxC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;YAC1B,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACzC,IAAI,KAAK,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBAChD,OAAO,CAAC,CAAC,IAAI,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;QACD,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,CAAU;IAC3D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,0CAA0C,MAAM,GAAG,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,87 @@
1
+ import type { Context } from "hono";
2
+ import type { ContentSecurityPolicyOptionHandler } from "hono/secure-headers";
3
+ export interface SecurityPluginOptions {
4
+ /** Enable secure response headers. Default: true. */
5
+ secureHeaders?: boolean | SecureHeadersOptions;
6
+ /** Content Security Policy. Default: restrictive baseline. Set `false` to disable (not recommended). */
7
+ csp?: boolean | CspOptions;
8
+ /** CSRF protection for form submissions. Default: true in production. */
9
+ csrf?: boolean | CsrfOptions;
10
+ /** CORS configuration. Default: disabled (same-origin only). */
11
+ cors?: false | CorsOptions;
12
+ /** Request body size limit. Default: 1mb. */
13
+ bodyLimit?: number | false;
14
+ /** Trusted hostnames. Requests with mismatched Host are rejected when set. */
15
+ trustedHosts?: string[];
16
+ /** Trusted origins for Origin/Referer validation on state-changing requests. */
17
+ trustedOrigins?: string[];
18
+ /** Rate limit provider. No limit when omitted. */
19
+ rateLimit?: RateLimitProvider;
20
+ /** Block open redirects from query params. Default: true. */
21
+ openRedirectGuard?: boolean | OpenRedirectOptions;
22
+ /** Proxy / forwarded header configuration. */
23
+ proxy?: ProxyOptions;
24
+ /** Secure cookie defaults applied via middleware hints. Default: true in production. */
25
+ secureCookies?: boolean | SecureCookieDefaults;
26
+ /** When true, reject obviously insecure production configuration. Default: true. */
27
+ strict?: boolean;
28
+ }
29
+ export interface SecureHeadersOptions {
30
+ /** Override individual secure-headers options from Hono. */
31
+ overrides?: Record<string, string | boolean>;
32
+ }
33
+ export interface CspOptions {
34
+ /** CSP directives merged onto secure defaults. */
35
+ directives?: Record<string, string | string[]>;
36
+ /** Report-only mode. */
37
+ reportOnly?: boolean;
38
+ reportUri?: string;
39
+ }
40
+ export interface CsrfOptions {
41
+ cookieName?: string;
42
+ /** Set explicitly — never defaults to false in production when csrf is enabled. */
43
+ secure?: boolean;
44
+ sameSite?: "Strict" | "Lax" | "None";
45
+ }
46
+ export interface CorsOptions {
47
+ origin: string | string[] | ((origin: string, c: Context) => string | null | undefined | Promise<string | null | undefined>);
48
+ allowMethods?: string[];
49
+ allowHeaders?: string[];
50
+ exposeHeaders?: string[];
51
+ maxAge?: number;
52
+ credentials?: boolean;
53
+ }
54
+ export interface OpenRedirectOptions {
55
+ /** Query parameter names to inspect. Default: ["redirect", "returnTo", "next", "url"]. */
56
+ params?: string[];
57
+ }
58
+ export interface ProxyOptions {
59
+ /** Trust X-Forwarded-* headers from these proxy hops. Default: 1 when behind a reverse proxy. */
60
+ trustProxy?: boolean | number;
61
+ /** Header used for client IP when rate limiting. Default: `x-forwarded-for`. */
62
+ clientIpHeader?: string;
63
+ }
64
+ export interface SecureCookieDefaults {
65
+ secure?: boolean;
66
+ httpOnly?: boolean;
67
+ sameSite?: "Strict" | "Lax" | "None";
68
+ path?: string;
69
+ }
70
+ export interface RateLimitContext {
71
+ key: string;
72
+ method: string;
73
+ path: string;
74
+ ip?: string;
75
+ }
76
+ export interface RateLimitResult {
77
+ allowed: boolean;
78
+ limit?: number;
79
+ remaining?: number;
80
+ resetAt?: number;
81
+ retryAfterSeconds?: number;
82
+ }
83
+ export interface RateLimitProvider {
84
+ check(ctx: RateLimitContext): RateLimitResult | Promise<RateLimitResult>;
85
+ }
86
+ export type { ContentSecurityPolicyOptionHandler };
87
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@kamod-ch/otok-security",
3
+ "version": "1.0.0",
4
+ "description": "Secure defaults and configurable security middleware for Otok apps.",
5
+ "homepage": "https://github.com/kamod-ch/otok/tree/main/packages/otok-security#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/kamod-ch/otok/issues"
8
+ },
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/kamod-ch/otok.git",
13
+ "directory": "packages/otok-security"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./middleware": {
26
+ "types": "./dist/middleware.d.ts",
27
+ "default": "./dist/middleware.js"
28
+ },
29
+ "./cookies": {
30
+ "types": "./dist/cookies.d.ts",
31
+ "default": "./dist/cookies.js"
32
+ },
33
+ "./redirect": {
34
+ "types": "./dist/redirect.d.ts",
35
+ "default": "./dist/redirect.js"
36
+ }
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "devDependencies": {
42
+ "@kamod-ch/otok": "1.0.0",
43
+ "@kamod-ch/otok-test": "1.0.0",
44
+ "@types/node": "^24.12.2",
45
+ "hono": "^4.13.8",
46
+ "typescript": "~6.0.2",
47
+ "vitest": "^4.1.2"
48
+ },
49
+ "peerDependencies": {
50
+ "@kamod-ch/otok": ">=0.6.1",
51
+ "hono": ">=4"
52
+ },
53
+ "engines": {
54
+ "node": ">=20"
55
+ },
56
+ "scripts": {
57
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
58
+ "typecheck": "tsc -p tsconfig.json --noEmit",
59
+ "test": "vitest run"
60
+ }
61
+ }