Skip to content

Commit 7b5387c

Browse files
fix(security): prevent fail-open in role permission checker (#23)
The `RolePermissionChecker.scopeMatches` method previously returned `true` by default for unhandled scope types. This could allow unauthorized access if a new scope type was added without updating the permission logic. This change enforces a "Fail Closed" strategy by returning `false` for any unknown scope types. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent 53e5ab5 commit 7b5387c

2 files changed

Lines changed: 13 additions & 7 deletions

File tree

.jules/sentinel.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Sentinel's Journal
2+
3+
## 2026-02-15 - Fail-Open Authorization Logic
4+
5+
**Vulnerability:** Found a "Fail Open" pattern in `RolePermissionChecker.scopeMatches` where unhandled scope types would default to `true` (allow access), potentially bypassing authorization checks if new scope types were added without updating the logic.
6+
**Learning:** Default return values in authorization checks must always be restrictive (`false` or `deny`). Code that relies on "falling through" to a default should fall to a safe state.
7+
**Prevention:** Always implement "Fail Closed" logic. When checking permissions, start with `false` and only switch to `true` if an explicit allow condition is met. Use exhaustive checks (like TypeScript's `never` check) in switch statements or if-else chains to ensure all cases are handled, but still default to `false` as a safety net.

app/domain/src/permission-checker.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,20 @@ export class RolePermissionChecker {
4646
if (roleScope.type !== requestedScope.type) return false
4747

4848
// For space scopes, check spaceId match
49-
if (roleScope.type === "space" && requestedScope.type === "space") {
49+
if (roleScope.type === "space" && requestedScope.type === "space")
5050
return roleScope.spaceId === requestedScope.spaceId
51-
}
5251

5352
// For group scopes, check groupId match
54-
if (roleScope.type === "group" && requestedScope.type === "group") {
53+
if (roleScope.type === "group" && requestedScope.type === "group")
5554
return roleScope.groupId === requestedScope.groupId
56-
}
5755

5856
// For workflow template scopes, check workflowTemplateId match
59-
if (roleScope.type === "workflow_template" && requestedScope.type === "workflow_template") {
57+
if (roleScope.type === "workflow_template" && requestedScope.type === "workflow_template")
6058
return roleScope.workflowTemplateId === requestedScope.workflowTemplateId
61-
}
6259

63-
return true
60+
// The conditions above should cover all possible cases, but to be sure
61+
// we default to false.
62+
return false
6463
}
6564

6665
static hasGroupPermission(

0 commit comments

Comments
 (0)