Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/managed-deny-floor-allowtransfer.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
---
"@objectstack/plugin-security": patch
---

test(plugin-security): the managed-deny floor now sees the evaluator's first grant route — `allowTransfer` (#14137)

The independent-property floor that derives which seeded default permission
sets MUST be managed-deny targets ("a default set whose `'*'` wildcard grants
a write", pinned in `default-permission-sets.test.ts` and diffed against
`MANAGED_DENY_TARGET_SETS`, #14029) read only the three CRUD write flags plus
`modifyAllRecords`. That missed the evaluator's FIRST grant route — the
direct bit read off `OPERATION_TO_PERMISSION` (`transfer: 'allowTransfer'`),
a real grant ENFORCED today through the insert/update `owner_id` door (#3004).
A future default set shaped `'*': { allowRead: true, allowTransfer: true }`
would have held ownership reassignment on every `managedBy: 'better-auth'`
identity table while tripping neither floor clause, so it was never required
to become a managed-deny target and would have kept its wildcard silently.

The floor now also checks `wc.allowTransfer === true` (a value test, never
key-existence — Zod materialises these bits with `.default(false)`, so they
are present-as-false; #14129 first review), and both exhaustive docblocks
name the first route. Zero behaviour delta today: no existing seeded set
carries a transfer-granting wildcard, every existing set keeps its exact
verdict (pinned), and the runtime deny application is byte-identical — this
hardens a CI-time pin, not the shipped permission surface.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,15 +82,21 @@ export const MANAGED_DENY_ENTRY = {
*
* Holding a write-granting `'*'` wildcard is the FLOOR of membership, not its
* definition: every default set whose wildcard grants any generic write class
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`) OR via
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`), OR
* via `allowTransfer` (#14137), the ownership-reassignment bit the
* evaluator's FIRST route reads directly off `OPERATION_TO_PERMISSION`
* (`transfer: 'allowTransfer'`, `permission-evaluator.ts`; a real grant,
* ENFORCED today through the insert/update `owner_id` door, #3004), OR via
* `modifyAllRecords`, whose super-user bypass grants edit/delete and the
* destructive class by the evaluator's second route (`MODIFY_ALL_WRITE_KEYS`,
* `permission-evaluator.ts`) — must be listed here (or carry a documented
* exclusion below), because the wildcard is what would otherwise grant raw
* writes on a newly-declared identity table — that floor is what
* `default-permission-sets.test.ts` derives independently and diffs against
* this list (#14029), so a future write-granting set that is not added here
* fails a pin instead of silently keeping its wildcard.
* fails a pin instead of silently keeping its wildcard. (The clause list is
* exhaustive over the evaluator's two known grant tables; a census of routes
* beyond them has not been done — #14137 "Not established".)
* Membership is WIDER than the floor: `viewer_readonly`'s wildcard is read-only
* and `member_default` has held none since #5491 — their injected entries are
* belt-and-suspenders and the read grant itself, respectively (see the module
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,26 +384,43 @@ describe('admin_full_access imports the kernel capability declaration unchanged
*/
describe('managed-deny targets — independent-property floor + registry union reaches the derived variant (#14029)', () => {
// Derived from `defaultPermissionSets`, never from the list under test.
// "Grants a write" in the evaluator's own terms: the three CRUD flags OR
// `modifyAllRecords` — the super-user bypass grants edit/delete and the
// destructive class by a second route (`MODIFY_ALL_WRITE_KEYS`,
// `permission-evaluator.ts`), so `'*': { modifyAllRecords: true }` is
// write-granting even with all three CRUD flags false. Value tests
// (`=== true`), not key-existence: Zod materialises the superuser bits with
// `.default(false)` (`permission.zod.ts`), so they are present-as-false.
const writeGrantingWildcardSets: string[] = defaultPermissionSets
.filter((s: any) => {
const wc = s.objects?.['*'];
return (
!!wc &&
(wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true)
);
})
.map((s) => s.name)
.sort();
// "Grants a write" in the evaluator's own terms — the bits its grant routes
// read (`permission-evaluator.ts`):
// - FIRST route, a direct bit read off `OPERATION_TO_PERMISSION`: the three
// CRUD write flags, plus `allowTransfer` (`transfer: 'allowTransfer'` —
// reassigning `owner_id`; a real grant, ENFORCED today through the
// insert/update owner_id door, #3004), so
// `'*': { allowRead: true, allowTransfer: true }` is write-granting even
// with all three CRUD flags AND `modifyAllRecords` false (#14137);
// - SECOND route, the `modifyAllRecords` super-user bypass, which grants
// edit/delete and the destructive class (`MODIFY_ALL_WRITE_KEYS`), so
// `'*': { modifyAllRecords: true }` is write-granting even with all three
// CRUD flags false.
// (A census of grant routes beyond these two tables has NOT been done —
// #14137 "Not established" — so this clause list is exhaustive over the two
// known routes, not a claim about the whole evaluator.)
// Value tests (`=== true`), not key-existence: Zod materialises these bits
// with `.default(false)` (`permission.zod.ts`), so they are present-as-false
// (#14129 first review; pinned below).
const grantsWildcardWrite = (wc: any): boolean =>
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true ||
wc.allowTransfer === true;

// The REAL derivation under pin — also applied to synthetic sets below so
// the floor is testable against shapes no seeded set carries yet.
const deriveWriteGrantingWildcardSets = (sets: readonly any[]): string[] =>
sets
.filter((s: any) => {
const wc = s.objects?.['*'];
return !!wc && grantsWildcardWrite(wc);
})
.map((s) => s.name)
.sort();

const writeGrantingWildcardSets: string[] = deriveWriteGrantingWildcardSets(defaultPermissionSets);

/**
* The one documented exclusion: `admin_full_access` keeps its unqualified
Expand DownExpand Up@@ -435,6 +452,77 @@ describe('managed-deny targets — independent-property floor + registry union r
expect([...MANAGED_DENY_TARGET_SETS]).not.toContain('admin_full_access');
});

// ── The floor judged against synthetic wildcards (#14137) ──
// `allowTransfer` is the evaluator's FIRST grant route (a direct bit read
// off `OPERATION_TO_PERMISSION`) and is ENFORCED today through the
// insert/update owner_id door (#3004): a transfer-only wildcard holds
// ownership reassignment on every managed identity table while all three
// CRUD write flags and `modifyAllRecords` are false — visible only in the
// evaluator's grant semantics, never in the flags the older clauses read.
// Every shape below goes through `PermissionSetSchema.parse` first so Zod
// materialises the `.default(false)` bits: the parsed wildcard carries the
// unauthored bits present-as-false, exactly what the seeded sets look like
// to the filter.
describe('the floor sees the evaluator first grant route — allowTransfer (#14137)', () => {
const parseProbeSet = (wildcard: Record<string, boolean>): any =>
PermissionSetSchema.parse({
name: 'synthetic_floor_probe',
label: 'Synthetic floor probe',
objects: { '*': wildcard },
});

it('a transfer-only wildcard is required to be a managed-deny target (the card)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: true });
const wc: any = probe.objects['*'];
// The shape really is the card's: all three CRUD write flags AND
// `modifyAllRecords` are (present-as-)false after parse.
expect(wc.allowCreate).toBe(false);
expect(wc.allowEdit).toBe(false);
expect(wc.allowDelete).toBe(false);
expect(wc.modifyAllRecords).toBe(false);
expect(wc.allowTransfer).toBe(true);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual(['synthetic_floor_probe']);
});

it('reverse control: a read-only wildcard is still NOT required', () => {
const probe = parseProbeSet({ allowRead: true });
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('present-as-false: an explicit allowTransfer:false wildcard does not trip the floor (value test, not key-existence)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: false });
const wc: any = probe.objects['*'];
// The key IS present after parse — `.default(false)` materialises it —
// so a key-existence rewrite of the floor turns exactly this pin red
// (#14129 first review; not to be re-litigated).
expect('allowTransfer' in wc).toBe(true);
expect(wc.allowTransfer).toBe(false);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('invariance: the allowTransfer clause changes no existing seeded set verdict (zero delta today)', () => {
// The pre-#14137 four-clause floor, restated ONLY to diff verdicts
// against: if a future seeded set legitimately relies on the
// `allowTransfer` clause, this pin goes red and the zero-delta claim is
// consciously retired — the same review moment the membership diff
// above forces.
let wildcardsSeen = 0;
for (const s of defaultPermissionSets as any[]) {
const wc = s.objects?.['*'];
if (!wc) continue;
wildcardsSeen += 1;
const preFloor =
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true;
expect(grantsWildcardWrite(wc), `verdict drifted for ${s.name}`).toBe(preFloor);
}
// Non-vacuousness: the loop really visited the seeded wildcards.
expect(wildcardsSeen).toBeGreaterThanOrEqual(3);
});
});

// ── The behaviour the membership buys, measured on the REAL derived set ──
// (clones so the module-level instances other tests read stay unmutated;
// the kernel path hands the same objects to the same function in place).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/managed-deny-floor-allowtransfer.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
---
"@objectstack/plugin-security": patch
---

test(plugin-security): the managed-deny floor now sees the evaluator's first grant route — `allowTransfer` (#14137)

The independent-property floor that derives which seeded default permission
sets MUST be managed-deny targets ("a default set whose `'*'` wildcard grants
a write", pinned in `default-permission-sets.test.ts` and diffed against
`MANAGED_DENY_TARGET_SETS`, #14029) read only the three CRUD write flags plus
`modifyAllRecords`. That missed the evaluator's FIRST grant route — the
direct bit read off `OPERATION_TO_PERMISSION` (`transfer: 'allowTransfer'`),
a real grant ENFORCED today through the insert/update `owner_id` door (#3004).
A future default set shaped `'*': { allowRead: true, allowTransfer: true }`
would have held ownership reassignment on every `managedBy: 'better-auth'`
identity table while tripping neither floor clause, so it was never required
to become a managed-deny target and would have kept its wildcard silently.

The floor now also checks `wc.allowTransfer === true` (a value test, never
key-existence — Zod materialises these bits with `.default(false)`, so they
are present-as-false; #14129 first review), and both exhaustive docblocks
name the first route. Zero behaviour delta today: no existing seeded set
carries a transfer-granting wildcard, every existing set keeps its exact
verdict (pinned), and the runtime deny application is byte-identical — this
hardens a CI-time pin, not the shipped permission surface.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,15 +82,21 @@ export const MANAGED_DENY_ENTRY = {
*
* Holding a write-granting `'*'` wildcard is the FLOOR of membership, not its
* definition: every default set whose wildcard grants any generic write class
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`) OR via
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`), OR
* via `allowTransfer` (#14137), the ownership-reassignment bit the
* evaluator's FIRST route reads directly off `OPERATION_TO_PERMISSION`
* (`transfer: 'allowTransfer'`, `permission-evaluator.ts`; a real grant,
* ENFORCED today through the insert/update `owner_id` door, #3004), OR via
* `modifyAllRecords`, whose super-user bypass grants edit/delete and the
* destructive class by the evaluator's second route (`MODIFY_ALL_WRITE_KEYS`,
* `permission-evaluator.ts`) — must be listed here (or carry a documented
* exclusion below), because the wildcard is what would otherwise grant raw
* writes on a newly-declared identity table — that floor is what
* `default-permission-sets.test.ts` derives independently and diffs against
* this list (#14029), so a future write-granting set that is not added here
* fails a pin instead of silently keeping its wildcard.
* fails a pin instead of silently keeping its wildcard. (The clause list is
* exhaustive over the evaluator's two known grant tables; a census of routes
* beyond them has not been done — #14137 "Not established".)
* Membership is WIDER than the floor: `viewer_readonly`'s wildcard is read-only
* and `member_default` has held none since #5491 — their injected entries are
* belt-and-suspenders and the read grant itself, respectively (see the module
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,26 +384,43 @@ describe('admin_full_access imports the kernel capability declaration unchanged
*/
describe('managed-deny targets — independent-property floor + registry union reaches the derived variant (#14029)', () => {
// Derived from `defaultPermissionSets`, never from the list under test.
// "Grants a write" in the evaluator's own terms: the three CRUD flags OR
// `modifyAllRecords` — the super-user bypass grants edit/delete and the
// destructive class by a second route (`MODIFY_ALL_WRITE_KEYS`,
// `permission-evaluator.ts`), so `'*': { modifyAllRecords: true }` is
// write-granting even with all three CRUD flags false. Value tests
// (`=== true`), not key-existence: Zod materialises the superuser bits with
// `.default(false)` (`permission.zod.ts`), so they are present-as-false.
const writeGrantingWildcardSets: string[] = defaultPermissionSets
.filter((s: any) => {
const wc = s.objects?.['*'];
return (
!!wc &&
(wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true)
);
})
.map((s) => s.name)
.sort();
// "Grants a write" in the evaluator's own terms — the bits its grant routes
// read (`permission-evaluator.ts`):
// - FIRST route, a direct bit read off `OPERATION_TO_PERMISSION`: the three
// CRUD write flags, plus `allowTransfer` (`transfer: 'allowTransfer'` —
// reassigning `owner_id`; a real grant, ENFORCED today through the
// insert/update owner_id door, #3004), so
// `'*': { allowRead: true, allowTransfer: true }` is write-granting even
// with all three CRUD flags AND `modifyAllRecords` false (#14137);
// - SECOND route, the `modifyAllRecords` super-user bypass, which grants
// edit/delete and the destructive class (`MODIFY_ALL_WRITE_KEYS`), so
// `'*': { modifyAllRecords: true }` is write-granting even with all three
// CRUD flags false.
// (A census of grant routes beyond these two tables has NOT been done —
// #14137 "Not established" — so this clause list is exhaustive over the two
// known routes, not a claim about the whole evaluator.)
// Value tests (`=== true`), not key-existence: Zod materialises these bits
// with `.default(false)` (`permission.zod.ts`), so they are present-as-false
// (#14129 first review; pinned below).
const grantsWildcardWrite = (wc: any): boolean =>
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true ||
wc.allowTransfer === true;

// The REAL derivation under pin — also applied to synthetic sets below so
// the floor is testable against shapes no seeded set carries yet.
const deriveWriteGrantingWildcardSets = (sets: readonly any[]): string[] =>
sets
.filter((s: any) => {
const wc = s.objects?.['*'];
return !!wc && grantsWildcardWrite(wc);
})
.map((s) => s.name)
.sort();

const writeGrantingWildcardSets: string[] = deriveWriteGrantingWildcardSets(defaultPermissionSets);

/**
* The one documented exclusion: `admin_full_access` keeps its unqualified
Expand DownExpand Up@@ -435,6 +452,77 @@ describe('managed-deny targets — independent-property floor + registry union r
expect([...MANAGED_DENY_TARGET_SETS]).not.toContain('admin_full_access');
});

// ── The floor judged against synthetic wildcards (#14137) ──
// `allowTransfer` is the evaluator's FIRST grant route (a direct bit read
// off `OPERATION_TO_PERMISSION`) and is ENFORCED today through the
// insert/update owner_id door (#3004): a transfer-only wildcard holds
// ownership reassignment on every managed identity table while all three
// CRUD write flags and `modifyAllRecords` are false — visible only in the
// evaluator's grant semantics, never in the flags the older clauses read.
// Every shape below goes through `PermissionSetSchema.parse` first so Zod
// materialises the `.default(false)` bits: the parsed wildcard carries the
// unauthored bits present-as-false, exactly what the seeded sets look like
// to the filter.
describe('the floor sees the evaluator first grant route — allowTransfer (#14137)', () => {
const parseProbeSet = (wildcard: Record<string, boolean>): any =>
PermissionSetSchema.parse({
name: 'synthetic_floor_probe',
label: 'Synthetic floor probe',
objects: { '*': wildcard },
});

it('a transfer-only wildcard is required to be a managed-deny target (the card)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: true });
const wc: any = probe.objects['*'];
// The shape really is the card's: all three CRUD write flags AND
// `modifyAllRecords` are (present-as-)false after parse.
expect(wc.allowCreate).toBe(false);
expect(wc.allowEdit).toBe(false);
expect(wc.allowDelete).toBe(false);
expect(wc.modifyAllRecords).toBe(false);
expect(wc.allowTransfer).toBe(true);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual(['synthetic_floor_probe']);
});

it('reverse control: a read-only wildcard is still NOT required', () => {
const probe = parseProbeSet({ allowRead: true });
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('present-as-false: an explicit allowTransfer:false wildcard does not trip the floor (value test, not key-existence)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: false });
const wc: any = probe.objects['*'];
// The key IS present after parse — `.default(false)` materialises it —
// so a key-existence rewrite of the floor turns exactly this pin red
// (#14129 first review; not to be re-litigated).
expect('allowTransfer' in wc).toBe(true);
expect(wc.allowTransfer).toBe(false);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('invariance: the allowTransfer clause changes no existing seeded set verdict (zero delta today)', () => {
// The pre-#14137 four-clause floor, restated ONLY to diff verdicts
// against: if a future seeded set legitimately relies on the
// `allowTransfer` clause, this pin goes red and the zero-delta claim is
// consciously retired — the same review moment the membership diff
// above forces.
let wildcardsSeen = 0;
for (const s of defaultPermissionSets as any[]) {
const wc = s.objects?.['*'];
if (!wc) continue;
wildcardsSeen += 1;
const preFloor =
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true;
expect(grantsWildcardWrite(wc), `verdict drifted for ${s.name}`).toBe(preFloor);
}
// Non-vacuousness: the loop really visited the seeded wildcards.
expect(wildcardsSeen).toBeGreaterThanOrEqual(3);
});
});

// ── The behaviour the membership buys, measured on the REAL derived set ──
// (clones so the module-level instances other tests read stay unmutated;
// the kernel path hands the same objects to the same function in place).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/managed-deny-floor-allowtransfer.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
---
"@objectstack/plugin-security": patch
---

test(plugin-security): the managed-deny floor now sees the evaluator's first grant route — `allowTransfer` (#14137)

The independent-property floor that derives which seeded default permission
sets MUST be managed-deny targets ("a default set whose `'*'` wildcard grants
a write", pinned in `default-permission-sets.test.ts` and diffed against
`MANAGED_DENY_TARGET_SETS`, #14029) read only the three CRUD write flags plus
`modifyAllRecords`. That missed the evaluator's FIRST grant route — the
direct bit read off `OPERATION_TO_PERMISSION` (`transfer: 'allowTransfer'`),
a real grant ENFORCED today through the insert/update `owner_id` door (#3004).
A future default set shaped `'*': { allowRead: true, allowTransfer: true }`
would have held ownership reassignment on every `managedBy: 'better-auth'`
identity table while tripping neither floor clause, so it was never required
to become a managed-deny target and would have kept its wildcard silently.

The floor now also checks `wc.allowTransfer === true` (a value test, never
key-existence — Zod materialises these bits with `.default(false)`, so they
are present-as-false; #14129 first review), and both exhaustive docblocks
name the first route. Zero behaviour delta today: no existing seeded set
carries a transfer-granting wildcard, every existing set keeps its exact
verdict (pinned), and the runtime deny application is byte-identical — this
hardens a CI-time pin, not the shipped permission surface.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,15 +82,21 @@ export const MANAGED_DENY_ENTRY = {
*
* Holding a write-granting `'*'` wildcard is the FLOOR of membership, not its
* definition: every default set whose wildcard grants any generic write class
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`) OR via
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`), OR
* via `allowTransfer` (#14137), the ownership-reassignment bit the
* evaluator's FIRST route reads directly off `OPERATION_TO_PERMISSION`
* (`transfer: 'allowTransfer'`, `permission-evaluator.ts`; a real grant,
* ENFORCED today through the insert/update `owner_id` door, #3004), OR via
* `modifyAllRecords`, whose super-user bypass grants edit/delete and the
* destructive class by the evaluator's second route (`MODIFY_ALL_WRITE_KEYS`,
* `permission-evaluator.ts`) — must be listed here (or carry a documented
* exclusion below), because the wildcard is what would otherwise grant raw
* writes on a newly-declared identity table — that floor is what
* `default-permission-sets.test.ts` derives independently and diffs against
* this list (#14029), so a future write-granting set that is not added here
* fails a pin instead of silently keeping its wildcard.
* fails a pin instead of silently keeping its wildcard. (The clause list is
* exhaustive over the evaluator's two known grant tables; a census of routes
* beyond them has not been done — #14137 "Not established".)
* Membership is WIDER than the floor: `viewer_readonly`'s wildcard is read-only
* and `member_default` has held none since #5491 — their injected entries are
* belt-and-suspenders and the read grant itself, respectively (see the module
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,26 +384,43 @@ describe('admin_full_access imports the kernel capability declaration unchanged
*/
describe('managed-deny targets — independent-property floor + registry union reaches the derived variant (#14029)', () => {
// Derived from `defaultPermissionSets`, never from the list under test.
// "Grants a write" in the evaluator's own terms: the three CRUD flags OR
// `modifyAllRecords` — the super-user bypass grants edit/delete and the
// destructive class by a second route (`MODIFY_ALL_WRITE_KEYS`,
// `permission-evaluator.ts`), so `'*': { modifyAllRecords: true }` is
// write-granting even with all three CRUD flags false. Value tests
// (`=== true`), not key-existence: Zod materialises the superuser bits with
// `.default(false)` (`permission.zod.ts`), so they are present-as-false.
const writeGrantingWildcardSets: string[] = defaultPermissionSets
.filter((s: any) => {
const wc = s.objects?.['*'];
return (
!!wc &&
(wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true)
);
})
.map((s) => s.name)
.sort();
// "Grants a write" in the evaluator's own terms — the bits its grant routes
// read (`permission-evaluator.ts`):
// - FIRST route, a direct bit read off `OPERATION_TO_PERMISSION`: the three
// CRUD write flags, plus `allowTransfer` (`transfer: 'allowTransfer'` —
// reassigning `owner_id`; a real grant, ENFORCED today through the
// insert/update owner_id door, #3004), so
// `'*': { allowRead: true, allowTransfer: true }` is write-granting even
// with all three CRUD flags AND `modifyAllRecords` false (#14137);
// - SECOND route, the `modifyAllRecords` super-user bypass, which grants
// edit/delete and the destructive class (`MODIFY_ALL_WRITE_KEYS`), so
// `'*': { modifyAllRecords: true }` is write-granting even with all three
// CRUD flags false.
// (A census of grant routes beyond these two tables has NOT been done —
// #14137 "Not established" — so this clause list is exhaustive over the two
// known routes, not a claim about the whole evaluator.)
// Value tests (`=== true`), not key-existence: Zod materialises these bits
// with `.default(false)` (`permission.zod.ts`), so they are present-as-false
// (#14129 first review; pinned below).
const grantsWildcardWrite = (wc: any): boolean =>
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true ||
wc.allowTransfer === true;

// The REAL derivation under pin — also applied to synthetic sets below so
// the floor is testable against shapes no seeded set carries yet.
const deriveWriteGrantingWildcardSets = (sets: readonly any[]): string[] =>
sets
.filter((s: any) => {
const wc = s.objects?.['*'];
return !!wc && grantsWildcardWrite(wc);
})
.map((s) => s.name)
.sort();

const writeGrantingWildcardSets: string[] = deriveWriteGrantingWildcardSets(defaultPermissionSets);

/**
* The one documented exclusion: `admin_full_access` keeps its unqualified
Expand DownExpand Up@@ -435,6 +452,77 @@ describe('managed-deny targets — independent-property floor + registry union r
expect([...MANAGED_DENY_TARGET_SETS]).not.toContain('admin_full_access');
});

// ── The floor judged against synthetic wildcards (#14137) ──
// `allowTransfer` is the evaluator's FIRST grant route (a direct bit read
// off `OPERATION_TO_PERMISSION`) and is ENFORCED today through the
// insert/update owner_id door (#3004): a transfer-only wildcard holds
// ownership reassignment on every managed identity table while all three
// CRUD write flags and `modifyAllRecords` are false — visible only in the
// evaluator's grant semantics, never in the flags the older clauses read.
// Every shape below goes through `PermissionSetSchema.parse` first so Zod
// materialises the `.default(false)` bits: the parsed wildcard carries the
// unauthored bits present-as-false, exactly what the seeded sets look like
// to the filter.
describe('the floor sees the evaluator first grant route — allowTransfer (#14137)', () => {
const parseProbeSet = (wildcard: Record<string, boolean>): any =>
PermissionSetSchema.parse({
name: 'synthetic_floor_probe',
label: 'Synthetic floor probe',
objects: { '*': wildcard },
});

it('a transfer-only wildcard is required to be a managed-deny target (the card)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: true });
const wc: any = probe.objects['*'];
// The shape really is the card's: all three CRUD write flags AND
// `modifyAllRecords` are (present-as-)false after parse.
expect(wc.allowCreate).toBe(false);
expect(wc.allowEdit).toBe(false);
expect(wc.allowDelete).toBe(false);
expect(wc.modifyAllRecords).toBe(false);
expect(wc.allowTransfer).toBe(true);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual(['synthetic_floor_probe']);
});

it('reverse control: a read-only wildcard is still NOT required', () => {
const probe = parseProbeSet({ allowRead: true });
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('present-as-false: an explicit allowTransfer:false wildcard does not trip the floor (value test, not key-existence)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: false });
const wc: any = probe.objects['*'];
// The key IS present after parse — `.default(false)` materialises it —
// so a key-existence rewrite of the floor turns exactly this pin red
// (#14129 first review; not to be re-litigated).
expect('allowTransfer' in wc).toBe(true);
expect(wc.allowTransfer).toBe(false);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('invariance: the allowTransfer clause changes no existing seeded set verdict (zero delta today)', () => {
// The pre-#14137 four-clause floor, restated ONLY to diff verdicts
// against: if a future seeded set legitimately relies on the
// `allowTransfer` clause, this pin goes red and the zero-delta claim is
// consciously retired — the same review moment the membership diff
// above forces.
let wildcardsSeen = 0;
for (const s of defaultPermissionSets as any[]) {
const wc = s.objects?.['*'];
if (!wc) continue;
wildcardsSeen += 1;
const preFloor =
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true;
expect(grantsWildcardWrite(wc), `verdict drifted for ${s.name}`).toBe(preFloor);
}
// Non-vacuousness: the loop really visited the seeded wildcards.
expect(wildcardsSeen).toBeGreaterThanOrEqual(3);
});
});

// ── The behaviour the membership buys, measured on the REAL derived set ──
// (clones so the module-level instances other tests read stay unmutated;
// the kernel path hands the same objects to the same function in place).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/managed-deny-floor-allowtransfer.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
---
"@objectstack/plugin-security": patch
---

test(plugin-security): the managed-deny floor now sees the evaluator's first grant route — `allowTransfer` (#14137)

The independent-property floor that derives which seeded default permission
sets MUST be managed-deny targets ("a default set whose `'*'` wildcard grants
a write", pinned in `default-permission-sets.test.ts` and diffed against
`MANAGED_DENY_TARGET_SETS`, #14029) read only the three CRUD write flags plus
`modifyAllRecords`. That missed the evaluator's FIRST grant route — the
direct bit read off `OPERATION_TO_PERMISSION` (`transfer: 'allowTransfer'`),
a real grant ENFORCED today through the insert/update `owner_id` door (#3004).
A future default set shaped `'*': { allowRead: true, allowTransfer: true }`
would have held ownership reassignment on every `managedBy: 'better-auth'`
identity table while tripping neither floor clause, so it was never required
to become a managed-deny target and would have kept its wildcard silently.

The floor now also checks `wc.allowTransfer === true` (a value test, never
key-existence — Zod materialises these bits with `.default(false)`, so they
are present-as-false; #14129 first review), and both exhaustive docblocks
name the first route. Zero behaviour delta today: no existing seeded set
carries a transfer-granting wildcard, every existing set keeps its exact
verdict (pinned), and the runtime deny application is byte-identical — this
hardens a CI-time pin, not the shipped permission surface.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,15 +82,21 @@ export const MANAGED_DENY_ENTRY = {
*
* Holding a write-granting `'*'` wildcard is the FLOOR of membership, not its
* definition: every default set whose wildcard grants any generic write class
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`) OR via
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`), OR
* via `allowTransfer` (#14137), the ownership-reassignment bit the
* evaluator's FIRST route reads directly off `OPERATION_TO_PERMISSION`
* (`transfer: 'allowTransfer'`, `permission-evaluator.ts`; a real grant,
* ENFORCED today through the insert/update `owner_id` door, #3004), OR via
* `modifyAllRecords`, whose super-user bypass grants edit/delete and the
* destructive class by the evaluator's second route (`MODIFY_ALL_WRITE_KEYS`,
* `permission-evaluator.ts`) — must be listed here (or carry a documented
* exclusion below), because the wildcard is what would otherwise grant raw
* writes on a newly-declared identity table — that floor is what
* `default-permission-sets.test.ts` derives independently and diffs against
* this list (#14029), so a future write-granting set that is not added here
* fails a pin instead of silently keeping its wildcard.
* fails a pin instead of silently keeping its wildcard. (The clause list is
* exhaustive over the evaluator's two known grant tables; a census of routes
* beyond them has not been done — #14137 "Not established".)
* Membership is WIDER than the floor: `viewer_readonly`'s wildcard is read-only
* and `member_default` has held none since #5491 — their injected entries are
* belt-and-suspenders and the read grant itself, respectively (see the module
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,26 +384,43 @@ describe('admin_full_access imports the kernel capability declaration unchanged
*/
describe('managed-deny targets — independent-property floor + registry union reaches the derived variant (#14029)', () => {
// Derived from `defaultPermissionSets`, never from the list under test.
// "Grants a write" in the evaluator's own terms: the three CRUD flags OR
// `modifyAllRecords` — the super-user bypass grants edit/delete and the
// destructive class by a second route (`MODIFY_ALL_WRITE_KEYS`,
// `permission-evaluator.ts`), so `'*': { modifyAllRecords: true }` is
// write-granting even with all three CRUD flags false. Value tests
// (`=== true`), not key-existence: Zod materialises the superuser bits with
// `.default(false)` (`permission.zod.ts`), so they are present-as-false.
const writeGrantingWildcardSets: string[] = defaultPermissionSets
.filter((s: any) => {
const wc = s.objects?.['*'];
return (
!!wc &&
(wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true)
);
})
.map((s) => s.name)
.sort();
// "Grants a write" in the evaluator's own terms — the bits its grant routes
// read (`permission-evaluator.ts`):
// - FIRST route, a direct bit read off `OPERATION_TO_PERMISSION`: the three
// CRUD write flags, plus `allowTransfer` (`transfer: 'allowTransfer'` —
// reassigning `owner_id`; a real grant, ENFORCED today through the
// insert/update owner_id door, #3004), so
// `'*': { allowRead: true, allowTransfer: true }` is write-granting even
// with all three CRUD flags AND `modifyAllRecords` false (#14137);
// - SECOND route, the `modifyAllRecords` super-user bypass, which grants
// edit/delete and the destructive class (`MODIFY_ALL_WRITE_KEYS`), so
// `'*': { modifyAllRecords: true }` is write-granting even with all three
// CRUD flags false.
// (A census of grant routes beyond these two tables has NOT been done —
// #14137 "Not established" — so this clause list is exhaustive over the two
// known routes, not a claim about the whole evaluator.)
// Value tests (`=== true`), not key-existence: Zod materialises these bits
// with `.default(false)` (`permission.zod.ts`), so they are present-as-false
// (#14129 first review; pinned below).
const grantsWildcardWrite = (wc: any): boolean =>
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true ||
wc.allowTransfer === true;

// The REAL derivation under pin — also applied to synthetic sets below so
// the floor is testable against shapes no seeded set carries yet.
const deriveWriteGrantingWildcardSets = (sets: readonly any[]): string[] =>
sets
.filter((s: any) => {
const wc = s.objects?.['*'];
return !!wc && grantsWildcardWrite(wc);
})
.map((s) => s.name)
.sort();

const writeGrantingWildcardSets: string[] = deriveWriteGrantingWildcardSets(defaultPermissionSets);

/**
* The one documented exclusion: `admin_full_access` keeps its unqualified
Expand DownExpand Up@@ -435,6 +452,77 @@ describe('managed-deny targets — independent-property floor + registry union r
expect([...MANAGED_DENY_TARGET_SETS]).not.toContain('admin_full_access');
});

// ── The floor judged against synthetic wildcards (#14137) ──
// `allowTransfer` is the evaluator's FIRST grant route (a direct bit read
// off `OPERATION_TO_PERMISSION`) and is ENFORCED today through the
// insert/update owner_id door (#3004): a transfer-only wildcard holds
// ownership reassignment on every managed identity table while all three
// CRUD write flags and `modifyAllRecords` are false — visible only in the
// evaluator's grant semantics, never in the flags the older clauses read.
// Every shape below goes through `PermissionSetSchema.parse` first so Zod
// materialises the `.default(false)` bits: the parsed wildcard carries the
// unauthored bits present-as-false, exactly what the seeded sets look like
// to the filter.
describe('the floor sees the evaluator first grant route — allowTransfer (#14137)', () => {
const parseProbeSet = (wildcard: Record<string, boolean>): any =>
PermissionSetSchema.parse({
name: 'synthetic_floor_probe',
label: 'Synthetic floor probe',
objects: { '*': wildcard },
});

it('a transfer-only wildcard is required to be a managed-deny target (the card)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: true });
const wc: any = probe.objects['*'];
// The shape really is the card's: all three CRUD write flags AND
// `modifyAllRecords` are (present-as-)false after parse.
expect(wc.allowCreate).toBe(false);
expect(wc.allowEdit).toBe(false);
expect(wc.allowDelete).toBe(false);
expect(wc.modifyAllRecords).toBe(false);
expect(wc.allowTransfer).toBe(true);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual(['synthetic_floor_probe']);
});

it('reverse control: a read-only wildcard is still NOT required', () => {
const probe = parseProbeSet({ allowRead: true });
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('present-as-false: an explicit allowTransfer:false wildcard does not trip the floor (value test, not key-existence)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: false });
const wc: any = probe.objects['*'];
// The key IS present after parse — `.default(false)` materialises it —
// so a key-existence rewrite of the floor turns exactly this pin red
// (#14129 first review; not to be re-litigated).
expect('allowTransfer' in wc).toBe(true);
expect(wc.allowTransfer).toBe(false);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('invariance: the allowTransfer clause changes no existing seeded set verdict (zero delta today)', () => {
// The pre-#14137 four-clause floor, restated ONLY to diff verdicts
// against: if a future seeded set legitimately relies on the
// `allowTransfer` clause, this pin goes red and the zero-delta claim is
// consciously retired — the same review moment the membership diff
// above forces.
let wildcardsSeen = 0;
for (const s of defaultPermissionSets as any[]) {
const wc = s.objects?.['*'];
if (!wc) continue;
wildcardsSeen += 1;
const preFloor =
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true;
expect(grantsWildcardWrite(wc), `verdict drifted for ${s.name}`).toBe(preFloor);
}
// Non-vacuousness: the loop really visited the seeded wildcards.
expect(wildcardsSeen).toBeGreaterThanOrEqual(3);
});
});

// ── The behaviour the membership buys, measured on the REAL derived set ──
// (clones so the module-level instances other tests read stay unmutated;
// the kernel path hands the same objects to the same function in place).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/managed-deny-floor-allowtransfer.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
---
"@objectstack/plugin-security": patch
---

test(plugin-security): the managed-deny floor now sees the evaluator's first grant route — `allowTransfer` (#14137)

The independent-property floor that derives which seeded default permission
sets MUST be managed-deny targets ("a default set whose `'*'` wildcard grants
a write", pinned in `default-permission-sets.test.ts` and diffed against
`MANAGED_DENY_TARGET_SETS`, #14029) read only the three CRUD write flags plus
`modifyAllRecords`. That missed the evaluator's FIRST grant route — the
direct bit read off `OPERATION_TO_PERMISSION` (`transfer: 'allowTransfer'`),
a real grant ENFORCED today through the insert/update `owner_id` door (#3004).
A future default set shaped `'*': { allowRead: true, allowTransfer: true }`
would have held ownership reassignment on every `managedBy: 'better-auth'`
identity table while tripping neither floor clause, so it was never required
to become a managed-deny target and would have kept its wildcard silently.

The floor now also checks `wc.allowTransfer === true` (a value test, never
key-existence — Zod materialises these bits with `.default(false)`, so they
are present-as-false; #14129 first review), and both exhaustive docblocks
name the first route. Zero behaviour delta today: no existing seeded set
carries a transfer-granting wildcard, every existing set keeps its exact
verdict (pinned), and the runtime deny application is byte-identical — this
hardens a CI-time pin, not the shipped permission surface.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,15 +82,21 @@ export const MANAGED_DENY_ENTRY = {
*
* Holding a write-granting `'*'` wildcard is the FLOOR of membership, not its
* definition: every default set whose wildcard grants any generic write class
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`) OR via
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`), OR
* via `allowTransfer` (#14137), the ownership-reassignment bit the
* evaluator's FIRST route reads directly off `OPERATION_TO_PERMISSION`
* (`transfer: 'allowTransfer'`, `permission-evaluator.ts`; a real grant,
* ENFORCED today through the insert/update `owner_id` door, #3004), OR via
* `modifyAllRecords`, whose super-user bypass grants edit/delete and the
* destructive class by the evaluator's second route (`MODIFY_ALL_WRITE_KEYS`,
* `permission-evaluator.ts`) — must be listed here (or carry a documented
* exclusion below), because the wildcard is what would otherwise grant raw
* writes on a newly-declared identity table — that floor is what
* `default-permission-sets.test.ts` derives independently and diffs against
* this list (#14029), so a future write-granting set that is not added here
* fails a pin instead of silently keeping its wildcard.
* fails a pin instead of silently keeping its wildcard. (The clause list is
* exhaustive over the evaluator's two known grant tables; a census of routes
* beyond them has not been done — #14137 "Not established".)
* Membership is WIDER than the floor: `viewer_readonly`'s wildcard is read-only
* and `member_default` has held none since #5491 — their injected entries are
* belt-and-suspenders and the read grant itself, respectively (see the module
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,26 +384,43 @@ describe('admin_full_access imports the kernel capability declaration unchanged
*/
describe('managed-deny targets — independent-property floor + registry union reaches the derived variant (#14029)', () => {
// Derived from `defaultPermissionSets`, never from the list under test.
// "Grants a write" in the evaluator's own terms: the three CRUD flags OR
// `modifyAllRecords` — the super-user bypass grants edit/delete and the
// destructive class by a second route (`MODIFY_ALL_WRITE_KEYS`,
// `permission-evaluator.ts`), so `'*': { modifyAllRecords: true }` is
// write-granting even with all three CRUD flags false. Value tests
// (`=== true`), not key-existence: Zod materialises the superuser bits with
// `.default(false)` (`permission.zod.ts`), so they are present-as-false.
const writeGrantingWildcardSets: string[] = defaultPermissionSets
.filter((s: any) => {
const wc = s.objects?.['*'];
return (
!!wc &&
(wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true)
);
})
.map((s) => s.name)
.sort();
// "Grants a write" in the evaluator's own terms — the bits its grant routes
// read (`permission-evaluator.ts`):
// - FIRST route, a direct bit read off `OPERATION_TO_PERMISSION`: the three
// CRUD write flags, plus `allowTransfer` (`transfer: 'allowTransfer'` —
// reassigning `owner_id`; a real grant, ENFORCED today through the
// insert/update owner_id door, #3004), so
// `'*': { allowRead: true, allowTransfer: true }` is write-granting even
// with all three CRUD flags AND `modifyAllRecords` false (#14137);
// - SECOND route, the `modifyAllRecords` super-user bypass, which grants
// edit/delete and the destructive class (`MODIFY_ALL_WRITE_KEYS`), so
// `'*': { modifyAllRecords: true }` is write-granting even with all three
// CRUD flags false.
// (A census of grant routes beyond these two tables has NOT been done —
// #14137 "Not established" — so this clause list is exhaustive over the two
// known routes, not a claim about the whole evaluator.)
// Value tests (`=== true`), not key-existence: Zod materialises these bits
// with `.default(false)` (`permission.zod.ts`), so they are present-as-false
// (#14129 first review; pinned below).
const grantsWildcardWrite = (wc: any): boolean =>
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true ||
wc.allowTransfer === true;

// The REAL derivation under pin — also applied to synthetic sets below so
// the floor is testable against shapes no seeded set carries yet.
const deriveWriteGrantingWildcardSets = (sets: readonly any[]): string[] =>
sets
.filter((s: any) => {
const wc = s.objects?.['*'];
return !!wc && grantsWildcardWrite(wc);
})
.map((s) => s.name)
.sort();

const writeGrantingWildcardSets: string[] = deriveWriteGrantingWildcardSets(defaultPermissionSets);

/**
* The one documented exclusion: `admin_full_access` keeps its unqualified
Expand DownExpand Up@@ -435,6 +452,77 @@ describe('managed-deny targets — independent-property floor + registry union r
expect([...MANAGED_DENY_TARGET_SETS]).not.toContain('admin_full_access');
});

// ── The floor judged against synthetic wildcards (#14137) ──
// `allowTransfer` is the evaluator's FIRST grant route (a direct bit read
// off `OPERATION_TO_PERMISSION`) and is ENFORCED today through the
// insert/update owner_id door (#3004): a transfer-only wildcard holds
// ownership reassignment on every managed identity table while all three
// CRUD write flags and `modifyAllRecords` are false — visible only in the
// evaluator's grant semantics, never in the flags the older clauses read.
// Every shape below goes through `PermissionSetSchema.parse` first so Zod
// materialises the `.default(false)` bits: the parsed wildcard carries the
// unauthored bits present-as-false, exactly what the seeded sets look like
// to the filter.
describe('the floor sees the evaluator first grant route — allowTransfer (#14137)', () => {
const parseProbeSet = (wildcard: Record<string, boolean>): any =>
PermissionSetSchema.parse({
name: 'synthetic_floor_probe',
label: 'Synthetic floor probe',
objects: { '*': wildcard },
});

it('a transfer-only wildcard is required to be a managed-deny target (the card)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: true });
const wc: any = probe.objects['*'];
// The shape really is the card's: all three CRUD write flags AND
// `modifyAllRecords` are (present-as-)false after parse.
expect(wc.allowCreate).toBe(false);
expect(wc.allowEdit).toBe(false);
expect(wc.allowDelete).toBe(false);
expect(wc.modifyAllRecords).toBe(false);
expect(wc.allowTransfer).toBe(true);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual(['synthetic_floor_probe']);
});

it('reverse control: a read-only wildcard is still NOT required', () => {
const probe = parseProbeSet({ allowRead: true });
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('present-as-false: an explicit allowTransfer:false wildcard does not trip the floor (value test, not key-existence)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: false });
const wc: any = probe.objects['*'];
// The key IS present after parse — `.default(false)` materialises it —
// so a key-existence rewrite of the floor turns exactly this pin red
// (#14129 first review; not to be re-litigated).
expect('allowTransfer' in wc).toBe(true);
expect(wc.allowTransfer).toBe(false);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('invariance: the allowTransfer clause changes no existing seeded set verdict (zero delta today)', () => {
// The pre-#14137 four-clause floor, restated ONLY to diff verdicts
// against: if a future seeded set legitimately relies on the
// `allowTransfer` clause, this pin goes red and the zero-delta claim is
// consciously retired — the same review moment the membership diff
// above forces.
let wildcardsSeen = 0;
for (const s of defaultPermissionSets as any[]) {
const wc = s.objects?.['*'];
if (!wc) continue;
wildcardsSeen += 1;
const preFloor =
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true;
expect(grantsWildcardWrite(wc), `verdict drifted for ${s.name}`).toBe(preFloor);
}
// Non-vacuousness: the loop really visited the seeded wildcards.
expect(wildcardsSeen).toBeGreaterThanOrEqual(3);
});
});

// ── The behaviour the membership buys, measured on the REAL derived set ──
// (clones so the module-level instances other tests read stay unmutated;
// the kernel path hands the same objects to the same function in place).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/managed-deny-floor-allowtransfer.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
---
"@objectstack/plugin-security": patch
---

test(plugin-security): the managed-deny floor now sees the evaluator's first grant route — `allowTransfer` (#14137)

The independent-property floor that derives which seeded default permission
sets MUST be managed-deny targets ("a default set whose `'*'` wildcard grants
a write", pinned in `default-permission-sets.test.ts` and diffed against
`MANAGED_DENY_TARGET_SETS`, #14029) read only the three CRUD write flags plus
`modifyAllRecords`. That missed the evaluator's FIRST grant route — the
direct bit read off `OPERATION_TO_PERMISSION` (`transfer: 'allowTransfer'`),
a real grant ENFORCED today through the insert/update `owner_id` door (#3004).
A future default set shaped `'*': { allowRead: true, allowTransfer: true }`
would have held ownership reassignment on every `managedBy: 'better-auth'`
identity table while tripping neither floor clause, so it was never required
to become a managed-deny target and would have kept its wildcard silently.

The floor now also checks `wc.allowTransfer === true` (a value test, never
key-existence — Zod materialises these bits with `.default(false)`, so they
are present-as-false; #14129 first review), and both exhaustive docblocks
name the first route. Zero behaviour delta today: no existing seeded set
carries a transfer-granting wildcard, every existing set keeps its exact
verdict (pinned), and the runtime deny application is byte-identical — this
hardens a CI-time pin, not the shipped permission surface.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,15 +82,21 @@ export const MANAGED_DENY_ENTRY = {
*
* Holding a write-granting `'*'` wildcard is the FLOOR of membership, not its
* definition: every default set whose wildcard grants any generic write class
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`) OR via
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`), OR
* via `allowTransfer` (#14137), the ownership-reassignment bit the
* evaluator's FIRST route reads directly off `OPERATION_TO_PERMISSION`
* (`transfer: 'allowTransfer'`, `permission-evaluator.ts`; a real grant,
* ENFORCED today through the insert/update `owner_id` door, #3004), OR via
* `modifyAllRecords`, whose super-user bypass grants edit/delete and the
* destructive class by the evaluator's second route (`MODIFY_ALL_WRITE_KEYS`,
* `permission-evaluator.ts`) — must be listed here (or carry a documented
* exclusion below), because the wildcard is what would otherwise grant raw
* writes on a newly-declared identity table — that floor is what
* `default-permission-sets.test.ts` derives independently and diffs against
* this list (#14029), so a future write-granting set that is not added here
* fails a pin instead of silently keeping its wildcard.
* fails a pin instead of silently keeping its wildcard. (The clause list is
* exhaustive over the evaluator's two known grant tables; a census of routes
* beyond them has not been done — #14137 "Not established".)
* Membership is WIDER than the floor: `viewer_readonly`'s wildcard is read-only
* and `member_default` has held none since #5491 — their injected entries are
* belt-and-suspenders and the read grant itself, respectively (see the module
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,26 +384,43 @@ describe('admin_full_access imports the kernel capability declaration unchanged
*/
describe('managed-deny targets — independent-property floor + registry union reaches the derived variant (#14029)', () => {
// Derived from `defaultPermissionSets`, never from the list under test.
// "Grants a write" in the evaluator's own terms: the three CRUD flags OR
// `modifyAllRecords` — the super-user bypass grants edit/delete and the
// destructive class by a second route (`MODIFY_ALL_WRITE_KEYS`,
// `permission-evaluator.ts`), so `'*': { modifyAllRecords: true }` is
// write-granting even with all three CRUD flags false. Value tests
// (`=== true`), not key-existence: Zod materialises the superuser bits with
// `.default(false)` (`permission.zod.ts`), so they are present-as-false.
const writeGrantingWildcardSets: string[] = defaultPermissionSets
.filter((s: any) => {
const wc = s.objects?.['*'];
return (
!!wc &&
(wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true)
);
})
.map((s) => s.name)
.sort();
// "Grants a write" in the evaluator's own terms — the bits its grant routes
// read (`permission-evaluator.ts`):
// - FIRST route, a direct bit read off `OPERATION_TO_PERMISSION`: the three
// CRUD write flags, plus `allowTransfer` (`transfer: 'allowTransfer'` —
// reassigning `owner_id`; a real grant, ENFORCED today through the
// insert/update owner_id door, #3004), so
// `'*': { allowRead: true, allowTransfer: true }` is write-granting even
// with all three CRUD flags AND `modifyAllRecords` false (#14137);
// - SECOND route, the `modifyAllRecords` super-user bypass, which grants
// edit/delete and the destructive class (`MODIFY_ALL_WRITE_KEYS`), so
// `'*': { modifyAllRecords: true }` is write-granting even with all three
// CRUD flags false.
// (A census of grant routes beyond these two tables has NOT been done —
// #14137 "Not established" — so this clause list is exhaustive over the two
// known routes, not a claim about the whole evaluator.)
// Value tests (`=== true`), not key-existence: Zod materialises these bits
// with `.default(false)` (`permission.zod.ts`), so they are present-as-false
// (#14129 first review; pinned below).
const grantsWildcardWrite = (wc: any): boolean =>
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true ||
wc.allowTransfer === true;

// The REAL derivation under pin — also applied to synthetic sets below so
// the floor is testable against shapes no seeded set carries yet.
const deriveWriteGrantingWildcardSets = (sets: readonly any[]): string[] =>
sets
.filter((s: any) => {
const wc = s.objects?.['*'];
return !!wc && grantsWildcardWrite(wc);
})
.map((s) => s.name)
.sort();

const writeGrantingWildcardSets: string[] = deriveWriteGrantingWildcardSets(defaultPermissionSets);

/**
* The one documented exclusion: `admin_full_access` keeps its unqualified
Expand DownExpand Up@@ -435,6 +452,77 @@ describe('managed-deny targets — independent-property floor + registry union r
expect([...MANAGED_DENY_TARGET_SETS]).not.toContain('admin_full_access');
});

// ── The floor judged against synthetic wildcards (#14137) ──
// `allowTransfer` is the evaluator's FIRST grant route (a direct bit read
// off `OPERATION_TO_PERMISSION`) and is ENFORCED today through the
// insert/update owner_id door (#3004): a transfer-only wildcard holds
// ownership reassignment on every managed identity table while all three
// CRUD write flags and `modifyAllRecords` are false — visible only in the
// evaluator's grant semantics, never in the flags the older clauses read.
// Every shape below goes through `PermissionSetSchema.parse` first so Zod
// materialises the `.default(false)` bits: the parsed wildcard carries the
// unauthored bits present-as-false, exactly what the seeded sets look like
// to the filter.
describe('the floor sees the evaluator first grant route — allowTransfer (#14137)', () => {
const parseProbeSet = (wildcard: Record<string, boolean>): any =>
PermissionSetSchema.parse({
name: 'synthetic_floor_probe',
label: 'Synthetic floor probe',
objects: { '*': wildcard },
});

it('a transfer-only wildcard is required to be a managed-deny target (the card)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: true });
const wc: any = probe.objects['*'];
// The shape really is the card's: all three CRUD write flags AND
// `modifyAllRecords` are (present-as-)false after parse.
expect(wc.allowCreate).toBe(false);
expect(wc.allowEdit).toBe(false);
expect(wc.allowDelete).toBe(false);
expect(wc.modifyAllRecords).toBe(false);
expect(wc.allowTransfer).toBe(true);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual(['synthetic_floor_probe']);
});

it('reverse control: a read-only wildcard is still NOT required', () => {
const probe = parseProbeSet({ allowRead: true });
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('present-as-false: an explicit allowTransfer:false wildcard does not trip the floor (value test, not key-existence)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: false });
const wc: any = probe.objects['*'];
// The key IS present after parse — `.default(false)` materialises it —
// so a key-existence rewrite of the floor turns exactly this pin red
// (#14129 first review; not to be re-litigated).
expect('allowTransfer' in wc).toBe(true);
expect(wc.allowTransfer).toBe(false);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('invariance: the allowTransfer clause changes no existing seeded set verdict (zero delta today)', () => {
// The pre-#14137 four-clause floor, restated ONLY to diff verdicts
// against: if a future seeded set legitimately relies on the
// `allowTransfer` clause, this pin goes red and the zero-delta claim is
// consciously retired — the same review moment the membership diff
// above forces.
let wildcardsSeen = 0;
for (const s of defaultPermissionSets as any[]) {
const wc = s.objects?.['*'];
if (!wc) continue;
wildcardsSeen += 1;
const preFloor =
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true;
expect(grantsWildcardWrite(wc), `verdict drifted for ${s.name}`).toBe(preFloor);
}
// Non-vacuousness: the loop really visited the seeded wildcards.
expect(wildcardsSeen).toBeGreaterThanOrEqual(3);
});
});

// ── The behaviour the membership buys, measured on the REAL derived set ──
// (clones so the module-level instances other tests read stay unmutated;
// the kernel path hands the same objects to the same function in place).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/managed-deny-floor-allowtransfer.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
---
"@objectstack/plugin-security": patch
---

test(plugin-security): the managed-deny floor now sees the evaluator's first grant route — `allowTransfer` (#14137)

The independent-property floor that derives which seeded default permission
sets MUST be managed-deny targets ("a default set whose `'*'` wildcard grants
a write", pinned in `default-permission-sets.test.ts` and diffed against
`MANAGED_DENY_TARGET_SETS`, #14029) read only the three CRUD write flags plus
`modifyAllRecords`. That missed the evaluator's FIRST grant route — the
direct bit read off `OPERATION_TO_PERMISSION` (`transfer: 'allowTransfer'`),
a real grant ENFORCED today through the insert/update `owner_id` door (#3004).
A future default set shaped `'*': { allowRead: true, allowTransfer: true }`
would have held ownership reassignment on every `managedBy: 'better-auth'`
identity table while tripping neither floor clause, so it was never required
to become a managed-deny target and would have kept its wildcard silently.

The floor now also checks `wc.allowTransfer === true` (a value test, never
key-existence — Zod materialises these bits with `.default(false)`, so they
are present-as-false; #14129 first review), and both exhaustive docblocks
name the first route. Zero behaviour delta today: no existing seeded set
carries a transfer-granting wildcard, every existing set keeps its exact
verdict (pinned), and the runtime deny application is byte-identical — this
hardens a CI-time pin, not the shipped permission surface.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,15 +82,21 @@ export const MANAGED_DENY_ENTRY = {
*
* Holding a write-granting `'*'` wildcard is the FLOOR of membership, not its
* definition: every default set whose wildcard grants any generic write class
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`) OR via
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`), OR
* via `allowTransfer` (#14137), the ownership-reassignment bit the
* evaluator's FIRST route reads directly off `OPERATION_TO_PERMISSION`
* (`transfer: 'allowTransfer'`, `permission-evaluator.ts`; a real grant,
* ENFORCED today through the insert/update `owner_id` door, #3004), OR via
* `modifyAllRecords`, whose super-user bypass grants edit/delete and the
* destructive class by the evaluator's second route (`MODIFY_ALL_WRITE_KEYS`,
* `permission-evaluator.ts`) — must be listed here (or carry a documented
* exclusion below), because the wildcard is what would otherwise grant raw
* writes on a newly-declared identity table — that floor is what
* `default-permission-sets.test.ts` derives independently and diffs against
* this list (#14029), so a future write-granting set that is not added here
* fails a pin instead of silently keeping its wildcard.
* fails a pin instead of silently keeping its wildcard. (The clause list is
* exhaustive over the evaluator's two known grant tables; a census of routes
* beyond them has not been done — #14137 "Not established".)
* Membership is WIDER than the floor: `viewer_readonly`'s wildcard is read-only
* and `member_default` has held none since #5491 — their injected entries are
* belt-and-suspenders and the read grant itself, respectively (see the module
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,26 +384,43 @@ describe('admin_full_access imports the kernel capability declaration unchanged
*/
describe('managed-deny targets — independent-property floor + registry union reaches the derived variant (#14029)', () => {
// Derived from `defaultPermissionSets`, never from the list under test.
// "Grants a write" in the evaluator's own terms: the three CRUD flags OR
// `modifyAllRecords` — the super-user bypass grants edit/delete and the
// destructive class by a second route (`MODIFY_ALL_WRITE_KEYS`,
// `permission-evaluator.ts`), so `'*': { modifyAllRecords: true }` is
// write-granting even with all three CRUD flags false. Value tests
// (`=== true`), not key-existence: Zod materialises the superuser bits with
// `.default(false)` (`permission.zod.ts`), so they are present-as-false.
const writeGrantingWildcardSets: string[] = defaultPermissionSets
.filter((s: any) => {
const wc = s.objects?.['*'];
return (
!!wc &&
(wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true)
);
})
.map((s) => s.name)
.sort();
// "Grants a write" in the evaluator's own terms — the bits its grant routes
// read (`permission-evaluator.ts`):
// - FIRST route, a direct bit read off `OPERATION_TO_PERMISSION`: the three
// CRUD write flags, plus `allowTransfer` (`transfer: 'allowTransfer'` —
// reassigning `owner_id`; a real grant, ENFORCED today through the
// insert/update owner_id door, #3004), so
// `'*': { allowRead: true, allowTransfer: true }` is write-granting even
// with all three CRUD flags AND `modifyAllRecords` false (#14137);
// - SECOND route, the `modifyAllRecords` super-user bypass, which grants
// edit/delete and the destructive class (`MODIFY_ALL_WRITE_KEYS`), so
// `'*': { modifyAllRecords: true }` is write-granting even with all three
// CRUD flags false.
// (A census of grant routes beyond these two tables has NOT been done —
// #14137 "Not established" — so this clause list is exhaustive over the two
// known routes, not a claim about the whole evaluator.)
// Value tests (`=== true`), not key-existence: Zod materialises these bits
// with `.default(false)` (`permission.zod.ts`), so they are present-as-false
// (#14129 first review; pinned below).
const grantsWildcardWrite = (wc: any): boolean =>
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true ||
wc.allowTransfer === true;

// The REAL derivation under pin — also applied to synthetic sets below so
// the floor is testable against shapes no seeded set carries yet.
const deriveWriteGrantingWildcardSets = (sets: readonly any[]): string[] =>
sets
.filter((s: any) => {
const wc = s.objects?.['*'];
return !!wc && grantsWildcardWrite(wc);
})
.map((s) => s.name)
.sort();

const writeGrantingWildcardSets: string[] = deriveWriteGrantingWildcardSets(defaultPermissionSets);

/**
* The one documented exclusion: `admin_full_access` keeps its unqualified
Expand DownExpand Up@@ -435,6 +452,77 @@ describe('managed-deny targets — independent-property floor + registry union r
expect([...MANAGED_DENY_TARGET_SETS]).not.toContain('admin_full_access');
});

// ── The floor judged against synthetic wildcards (#14137) ──
// `allowTransfer` is the evaluator's FIRST grant route (a direct bit read
// off `OPERATION_TO_PERMISSION`) and is ENFORCED today through the
// insert/update owner_id door (#3004): a transfer-only wildcard holds
// ownership reassignment on every managed identity table while all three
// CRUD write flags and `modifyAllRecords` are false — visible only in the
// evaluator's grant semantics, never in the flags the older clauses read.
// Every shape below goes through `PermissionSetSchema.parse` first so Zod
// materialises the `.default(false)` bits: the parsed wildcard carries the
// unauthored bits present-as-false, exactly what the seeded sets look like
// to the filter.
describe('the floor sees the evaluator first grant route — allowTransfer (#14137)', () => {
const parseProbeSet = (wildcard: Record<string, boolean>): any =>
PermissionSetSchema.parse({
name: 'synthetic_floor_probe',
label: 'Synthetic floor probe',
objects: { '*': wildcard },
});

it('a transfer-only wildcard is required to be a managed-deny target (the card)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: true });
const wc: any = probe.objects['*'];
// The shape really is the card's: all three CRUD write flags AND
// `modifyAllRecords` are (present-as-)false after parse.
expect(wc.allowCreate).toBe(false);
expect(wc.allowEdit).toBe(false);
expect(wc.allowDelete).toBe(false);
expect(wc.modifyAllRecords).toBe(false);
expect(wc.allowTransfer).toBe(true);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual(['synthetic_floor_probe']);
});

it('reverse control: a read-only wildcard is still NOT required', () => {
const probe = parseProbeSet({ allowRead: true });
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('present-as-false: an explicit allowTransfer:false wildcard does not trip the floor (value test, not key-existence)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: false });
const wc: any = probe.objects['*'];
// The key IS present after parse — `.default(false)` materialises it —
// so a key-existence rewrite of the floor turns exactly this pin red
// (#14129 first review; not to be re-litigated).
expect('allowTransfer' in wc).toBe(true);
expect(wc.allowTransfer).toBe(false);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('invariance: the allowTransfer clause changes no existing seeded set verdict (zero delta today)', () => {
// The pre-#14137 four-clause floor, restated ONLY to diff verdicts
// against: if a future seeded set legitimately relies on the
// `allowTransfer` clause, this pin goes red and the zero-delta claim is
// consciously retired — the same review moment the membership diff
// above forces.
let wildcardsSeen = 0;
for (const s of defaultPermissionSets as any[]) {
const wc = s.objects?.['*'];
if (!wc) continue;
wildcardsSeen += 1;
const preFloor =
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true;
expect(grantsWildcardWrite(wc), `verdict drifted for ${s.name}`).toBe(preFloor);
}
// Non-vacuousness: the loop really visited the seeded wildcards.
expect(wildcardsSeen).toBeGreaterThanOrEqual(3);
});
});

// ── The behaviour the membership buys, measured on the REAL derived set ──
// (clones so the module-level instances other tests read stay unmutated;
// the kernel path hands the same objects to the same function in place).
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/managed-deny-floor-allowtransfer.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
---
"@objectstack/plugin-security": patch
---

test(plugin-security): the managed-deny floor now sees the evaluator's first grant route — `allowTransfer` (#14137)

The independent-property floor that derives which seeded default permission
sets MUST be managed-deny targets ("a default set whose `'*'` wildcard grants
a write", pinned in `default-permission-sets.test.ts` and diffed against
`MANAGED_DENY_TARGET_SETS`, #14029) read only the three CRUD write flags plus
`modifyAllRecords`. That missed the evaluator's FIRST grant route — the
direct bit read off `OPERATION_TO_PERMISSION` (`transfer: 'allowTransfer'`),
a real grant ENFORCED today through the insert/update `owner_id` door (#3004).
A future default set shaped `'*': { allowRead: true, allowTransfer: true }`
would have held ownership reassignment on every `managedBy: 'better-auth'`
identity table while tripping neither floor clause, so it was never required
to become a managed-deny target and would have kept its wildcard silently.

The floor now also checks `wc.allowTransfer === true` (a value test, never
key-existence — Zod materialises these bits with `.default(false)`, so they
are present-as-false; #14129 first review), and both exhaustive docblocks
name the first route. Zero behaviour delta today: no existing seeded set
carries a transfer-granting wildcard, every existing set keeps its exact
verdict (pinned), and the runtime deny application is byte-identical — this
hardens a CI-time pin, not the shipped permission surface.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,15 +82,21 @@ export const MANAGED_DENY_ENTRY = {
*
* Holding a write-granting `'*'` wildcard is the FLOOR of membership, not its
* definition: every default set whose wildcard grants any generic write class
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`) OR via
* — via the three write flags (`allowCreate`/`allowEdit`/`allowDelete`), OR
* via `allowTransfer` (#14137), the ownership-reassignment bit the
* evaluator's FIRST route reads directly off `OPERATION_TO_PERMISSION`
* (`transfer: 'allowTransfer'`, `permission-evaluator.ts`; a real grant,
* ENFORCED today through the insert/update `owner_id` door, #3004), OR via
* `modifyAllRecords`, whose super-user bypass grants edit/delete and the
* destructive class by the evaluator's second route (`MODIFY_ALL_WRITE_KEYS`,
* `permission-evaluator.ts`) — must be listed here (or carry a documented
* exclusion below), because the wildcard is what would otherwise grant raw
* writes on a newly-declared identity table — that floor is what
* `default-permission-sets.test.ts` derives independently and diffs against
* this list (#14029), so a future write-granting set that is not added here
* fails a pin instead of silently keeping its wildcard.
* fails a pin instead of silently keeping its wildcard. (The clause list is
* exhaustive over the evaluator's two known grant tables; a census of routes
* beyond them has not been done — #14137 "Not established".)
* Membership is WIDER than the floor: `viewer_readonly`'s wildcard is read-only
* and `member_default` has held none since #5491 — their injected entries are
* belt-and-suspenders and the read grant itself, respectively (see the module
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,26 +384,43 @@ describe('admin_full_access imports the kernel capability declaration unchanged
*/
describe('managed-deny targets — independent-property floor + registry union reaches the derived variant (#14029)', () => {
// Derived from `defaultPermissionSets`, never from the list under test.
// "Grants a write" in the evaluator's own terms: the three CRUD flags OR
// `modifyAllRecords` — the super-user bypass grants edit/delete and the
// destructive class by a second route (`MODIFY_ALL_WRITE_KEYS`,
// `permission-evaluator.ts`), so `'*': { modifyAllRecords: true }` is
// write-granting even with all three CRUD flags false. Value tests
// (`=== true`), not key-existence: Zod materialises the superuser bits with
// `.default(false)` (`permission.zod.ts`), so they are present-as-false.
const writeGrantingWildcardSets: string[] = defaultPermissionSets
.filter((s: any) => {
const wc = s.objects?.['*'];
return (
!!wc &&
(wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true)
);
})
.map((s) => s.name)
.sort();
// "Grants a write" in the evaluator's own terms — the bits its grant routes
// read (`permission-evaluator.ts`):
// - FIRST route, a direct bit read off `OPERATION_TO_PERMISSION`: the three
// CRUD write flags, plus `allowTransfer` (`transfer: 'allowTransfer'` —
// reassigning `owner_id`; a real grant, ENFORCED today through the
// insert/update owner_id door, #3004), so
// `'*': { allowRead: true, allowTransfer: true }` is write-granting even
// with all three CRUD flags AND `modifyAllRecords` false (#14137);
// - SECOND route, the `modifyAllRecords` super-user bypass, which grants
// edit/delete and the destructive class (`MODIFY_ALL_WRITE_KEYS`), so
// `'*': { modifyAllRecords: true }` is write-granting even with all three
// CRUD flags false.
// (A census of grant routes beyond these two tables has NOT been done —
// #14137 "Not established" — so this clause list is exhaustive over the two
// known routes, not a claim about the whole evaluator.)
// Value tests (`=== true`), not key-existence: Zod materialises these bits
// with `.default(false)` (`permission.zod.ts`), so they are present-as-false
// (#14129 first review; pinned below).
const grantsWildcardWrite = (wc: any): boolean =>
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true ||
wc.allowTransfer === true;

// The REAL derivation under pin — also applied to synthetic sets below so
// the floor is testable against shapes no seeded set carries yet.
const deriveWriteGrantingWildcardSets = (sets: readonly any[]): string[] =>
sets
.filter((s: any) => {
const wc = s.objects?.['*'];
return !!wc && grantsWildcardWrite(wc);
})
.map((s) => s.name)
.sort();

const writeGrantingWildcardSets: string[] = deriveWriteGrantingWildcardSets(defaultPermissionSets);

/**
* The one documented exclusion: `admin_full_access` keeps its unqualified
Expand DownExpand Up@@ -435,6 +452,77 @@ describe('managed-deny targets — independent-property floor + registry union r
expect([...MANAGED_DENY_TARGET_SETS]).not.toContain('admin_full_access');
});

// ── The floor judged against synthetic wildcards (#14137) ──
// `allowTransfer` is the evaluator's FIRST grant route (a direct bit read
// off `OPERATION_TO_PERMISSION`) and is ENFORCED today through the
// insert/update owner_id door (#3004): a transfer-only wildcard holds
// ownership reassignment on every managed identity table while all three
// CRUD write flags and `modifyAllRecords` are false — visible only in the
// evaluator's grant semantics, never in the flags the older clauses read.
// Every shape below goes through `PermissionSetSchema.parse` first so Zod
// materialises the `.default(false)` bits: the parsed wildcard carries the
// unauthored bits present-as-false, exactly what the seeded sets look like
// to the filter.
describe('the floor sees the evaluator first grant route — allowTransfer (#14137)', () => {
const parseProbeSet = (wildcard: Record<string, boolean>): any =>
PermissionSetSchema.parse({
name: 'synthetic_floor_probe',
label: 'Synthetic floor probe',
objects: { '*': wildcard },
});

it('a transfer-only wildcard is required to be a managed-deny target (the card)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: true });
const wc: any = probe.objects['*'];
// The shape really is the card's: all three CRUD write flags AND
// `modifyAllRecords` are (present-as-)false after parse.
expect(wc.allowCreate).toBe(false);
expect(wc.allowEdit).toBe(false);
expect(wc.allowDelete).toBe(false);
expect(wc.modifyAllRecords).toBe(false);
expect(wc.allowTransfer).toBe(true);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual(['synthetic_floor_probe']);
});

it('reverse control: a read-only wildcard is still NOT required', () => {
const probe = parseProbeSet({ allowRead: true });
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('present-as-false: an explicit allowTransfer:false wildcard does not trip the floor (value test, not key-existence)', () => {
const probe = parseProbeSet({ allowRead: true, allowTransfer: false });
const wc: any = probe.objects['*'];
// The key IS present after parse — `.default(false)` materialises it —
// so a key-existence rewrite of the floor turns exactly this pin red
// (#14129 first review; not to be re-litigated).
expect('allowTransfer' in wc).toBe(true);
expect(wc.allowTransfer).toBe(false);
expect(deriveWriteGrantingWildcardSets([probe])).toEqual([]);
});

it('invariance: the allowTransfer clause changes no existing seeded set verdict (zero delta today)', () => {
// The pre-#14137 four-clause floor, restated ONLY to diff verdicts
// against: if a future seeded set legitimately relies on the
// `allowTransfer` clause, this pin goes red and the zero-delta claim is
// consciously retired — the same review moment the membership diff
// above forces.
let wildcardsSeen = 0;
for (const s of defaultPermissionSets as any[]) {
const wc = s.objects?.['*'];
if (!wc) continue;
wildcardsSeen += 1;
const preFloor =
wc.allowCreate === true ||
wc.allowEdit === true ||
wc.allowDelete === true ||
wc.modifyAllRecords === true;
expect(grantsWildcardWrite(wc), `verdict drifted for ${s.name}`).toBe(preFloor);
}
// Non-vacuousness: the loop really visited the seeded wildcards.
expect(wildcardsSeen).toBeGreaterThanOrEqual(3);
});
});

// ── The behaviour the membership buys, measured on the REAL derived set ──
// (clones so the module-level instances other tests read stay unmutated;
// the kernel path hands the same objects to the same function in place).
Expand Down
Loading