UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

408 lines (350 loc) 11.6 kB
import { Alepha } from "alepha"; import { describe, expect, it } from "vitest"; import { InvalidPermissionError, SecurityProvider } from "../index.ts"; /** * Bug #3: Regex Vulnerability in PERMISSION_REGEXP_WILDCARD * * Issue: The regex had incorrect operator precedence: * OLD: /^[\w-]+((:[\w-]+)+)?|(:\*)$/ * This created two patterns: /^[\w-]+((:[\w-]+)+)?/ OR /(:\*)$/ * The second part matched ":*" anywhere without start anchor. * * FIXED: /^[\w-]+((:[\w-]+)*:\*|(:[\w-]+)+)?$/ * Now correctly matches: start, then word chars/hyphens, then optionally either: * - (zero or more groups):* (for wildcards like "admin:api:*") * - (one or more groups) (for exact permissions like "users:read") * - nothing (for simple permissions like "read") * * Expected: Invalid patterns like standalone ":*" should be rejected. */ describe("Bug #3: Regex Vulnerability in PERMISSION_REGEXP_WILDCARD", () => { describe("Valid wildcard patterns (should pass)", () => { it("should accept global wildcard", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); // Global wildcard is handled separately, but still valid expect(() => sec.createRole({ name: "admin", permissions: [{ name: "*" }], }), ).not.toThrow(); }); it("should accept simple wildcard", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "user", permissions: [{ name: "users:*" }], }), ).not.toThrow(); }); it("should accept multi-layer wildcard", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "admin", permissions: [ { name: "admin:api:*" }, { name: "admin:api:v1:*" }, { name: "admin:api:v1:users:*" }, ], }), ).not.toThrow(); }); it("should accept regular permissions with groups", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "user", permissions: [ { name: "users:read" }, { name: "admin:api:users:read" }, { name: "a:b:c:d:e:f" }, ], }), ).not.toThrow(); }); it("should accept permissions with hyphens", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "user", permissions: [ { name: "user-management:*" }, { name: "api-v1:users:*" }, { name: "admin-portal:api-gateway:*" }, ], }), ).not.toThrow(); }); it("should accept permissions with underscores", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "user", permissions: [ { name: "user_management:*" }, { name: "api_v1:users:*" }, ], }), ).not.toThrow(); }); }); describe("Invalid wildcard patterns (should be rejected)", () => { it("should reject standalone :*", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); // This was the bug - old regex accepted ":*" without prefix expect(() => sec.createRole({ name: "invalid", permissions: [{ name: ":*" }], }), ).toThrow(InvalidPermissionError); }); it("should reject pattern starting with colon", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "invalid", permissions: [{ name: ":users" }], }), ).toThrow(InvalidPermissionError); }); it("should reject pattern ending with colon (no wildcard)", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "invalid", permissions: [{ name: "users:" }], }), ).toThrow(InvalidPermissionError); }); it("should reject pattern with double colons", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "invalid", permissions: [{ name: "users::read" }], }), ).toThrow(InvalidPermissionError); }); it("should reject empty string", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "invalid", permissions: [{ name: "" }], }), ).toThrow(InvalidPermissionError); }); it("should reject just a colon", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "invalid", permissions: [{ name: ":" }], }), ).toThrow(InvalidPermissionError); }); it("should reject special characters", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); const invalidChars = [ "users@admin:*", "users#read", "users.read:*", "users/read", "users read", // space "users!:*", "admin$:*", "api%:users", ]; for (const invalid of invalidChars) { expect(() => sec.createRole({ name: "invalid", permissions: [{ name: invalid }], }), ).toThrow(InvalidPermissionError); } }); it("should reject pattern with multiple wildcards", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "invalid", permissions: [{ name: "users:*:*" }], }), ).toThrow(InvalidPermissionError); }); it("should reject wildcard not at the end", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "invalid", permissions: [{ name: "users:*:read" }], }), ).toThrow(InvalidPermissionError); }); it("should reject pattern ending with multiple colons", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "invalid", permissions: [{ name: "users::" }], }), ).toThrow(InvalidPermissionError); }); it("should reject pattern starting with multiple colons", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "invalid", permissions: [{ name: "::users" }], }), ).toThrow(InvalidPermissionError); }); }); describe("Edge cases for regex validation", () => { it("should accept numbers in permission names", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); expect(() => sec.createRole({ name: "user", permissions: [ { name: "api123:*" }, { name: "v1:users:*" }, { name: "resource2024:read" }, ], }), ).not.toThrow(); }); it("should accept very long permission names", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); const longPart = "a".repeat(100); expect(() => sec.createRole({ name: "user", permissions: [{ name: `${longPart}:*` }], }), ).not.toThrow(); }); it("should accept deeply nested permissions", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); const deep = Array(20).fill("level").join(":"); expect(() => sec.createRole({ name: "user", permissions: [{ name: `${deep}:*` }], }), ).not.toThrow(); }); it("should properly validate permission objects (not just strings)", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); // When creating permissions from objects, they go through the same validation expect(() => sec.createPermission({ name: ":invalid" })).toThrow( InvalidPermissionError, ); expect(() => sec.createPermission({ name: "users:" })).toThrow( InvalidPermissionError, ); expect(() => sec.createPermission({ name: "users::read" })).toThrow( InvalidPermissionError, ); }); it("should validate wildcards only appear in role permissions, not created permissions", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); // You cannot create a permission with a wildcard expect(() => sec.createPermission("users:*")).toThrow( InvalidPermissionError, ); // But you can use wildcards in roles sec.createPermission("users:read"); expect(() => sec.createRole({ name: "user", permissions: [{ name: "users:*" }], }), ).not.toThrow(); }); }); describe("Regex security validation", () => { it("should ensure start anchor is enforced", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); // These should fail because they don't start with valid chars expect(() => sec.createRole({ name: "invalid", permissions: [{ name: " users:*" }], // leading space }), ).toThrow(InvalidPermissionError); expect(() => sec.createRole({ name: "invalid", permissions: [{ name: "\nusers:*" }], // leading newline }), ).toThrow(InvalidPermissionError); }); it("should ensure end anchor is enforced", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); // These should fail because they don't end properly expect(() => sec.createRole({ name: "invalid", permissions: [{ name: "users:* " }], // trailing space }), ).toThrow(InvalidPermissionError); expect(() => sec.createRole({ name: "invalid", permissions: [{ name: "users:*\n" }], // trailing newline }), ).toThrow(InvalidPermissionError); }); it("should not allow regex injection attempts", () => { const app = Alepha.create(); const sec = app.inject(SecurityProvider); const injectionAttempts = [ "users.*:*", // regex wildcard "users[a-z]:*", // regex character class "users|admin:*", // regex alternation "users(admin):*", // regex group "users+:*", // regex quantifier "users?:*", // regex quantifier "users^:*", // regex anchor "users$:*", // regex anchor ]; for (const attempt of injectionAttempts) { expect(() => sec.createRole({ name: "invalid", permissions: [{ name: attempt }], }), ).toThrow(InvalidPermissionError); } }); }); });