alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
408 lines (350 loc) • 11.6 kB
text/typescript
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);
}
});
});
});