<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[XRUIOS Permissions]]></title><description><![CDATA[Obsidian digital garden]]></description><link>http://github.com/dylang/node-rss</link><image><url>site-lib/media/favicon.png</url><title>XRUIOS Permissions</title><link></link></image><generator>Webpage HTML Export plugin for Obsidian</generator><lastBuildDate>Mon, 27 Jul 2026 18:37:01 GMT</lastBuildDate><atom:link href="site-lib/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 27 Jul 2026 18:36:59 GMT</pubDate><ttl>60</ttl><dc:creator></dc:creator><item><title><![CDATA[1 - Welcome]]></title><description><![CDATA[ <img alt="XRUIOS Permissions" src="assets/xruios-perms.png" referrerpolicy="no-referrer" target="_self"> A per-device, fail-closed permission system for XRUIOS — apps ask, the store answers, and nothing is trusted just because it says so.XRUIOS Permissions is the access-control layer that sits under the XRUIOS orchestration surface. Any app running on a device can request an action ("read the camera", "edit camera footage", "change a theme"), and this library decides — from an encrypted-at-rest store — whether that app is actually allowed.It uses Pariah Cybersecurity for its SecureData type and encrypted storage, so keys, requester IDs and permission values never sit in plaintext in RAM longer than they have to.At a high level, three things exist:
<br>A permission store — one encrypted file per permission class (usually one per app), created once via <a data-href="1 - Setting Up A Permission Store" href="2-how-to-use/1-setting-up-a-permission-store.html" class="internal-link" target="_self" rel="noopener nofollow">1 - Setting Up A Permission Store</a>.
<br>Grants — an app (its requester ID) is given a permission (a name like "Edit Camera Footage") at an integer level. See <a data-href="2 - Granting, Checking and Revoking" href="2-how-to-use/2-granting,-checking-and-revoking.html" class="internal-link" target="_self" rel="noopener nofollow">2 - Granting, Checking and Revoking</a>.
<br>Checks — before an action runs, the code asks "does this requester have this permission at (at least) this level?" — ideally without writing that check by hand in every function, which is what <a data-href="3 - Auto-Gating Functions (The Attribute)" href="2-how-to-use/3-auto-gating-functions-(the-attribute).html" class="internal-link" target="_self" rel="noopener nofollow">3 - Auto-Gating Functions (The Attribute)</a> is for.
<br>Behind the scenes it caches results, rate-limits abusive callers, and serializes writes so two changes can't clobber each other — all covered in <a data-href="1 - How Caching and Rate Limiting Work" href="3-extras/1-how-caching-and-rate-limiting-work.html" class="internal-link" target="_self" rel="noopener nofollow">1 - How Caching and Rate Limiting Work</a>.XRUIOS Permissions follows four rules: Never trust the UI — identity comes from the Manager, not the caller. Fail closed — if the store is missing, unreadable, or tampered with, the answer is no. <br>
A denial should never hide a problem — a "no" caused by a bad key or a corrupted store is surfaced, not silently swallowed. See <a data-href="2 - Security Model and Threat Notes" href="3-extras/2-security-model-and-threat-notes.html" class="internal-link" target="_self" rel="noopener nofollow">2 - Security Model and Threat Notes</a>. Gate everything, cheaply — checking a permission should be one attribute, not twenty lines. “An OS that trusts what the window tells it isn't an OS, it's a suggestion box.” Pariah Cybersecurity (and its dependencies) — provides SecureData, SecureCache, and the encrypted JSON store (JSONDataHandler).
XRUIOS.Manager — issues requester IDs and per-app encryption keys, and is the intended place the auto-gating attribute is consumed.
This library decides which requester's permissions to read and enforces that a caller holds the correct per-app key. It does not, on its own, prove that a running process is the app it claims to be — that attestation is the Manager's job, and it's why requester IDs and keys are Manager-issued rather than UI-supplied.<br>Read <a data-href="2 - Security Model and Threat Notes" href="3-extras/2-security-model-and-threat-notes.html" class="internal-link" target="_self" rel="noopener nofollow">2 - Security Model and Threat Notes</a> before you wire this into anything that guards a real device resource (camera, mic, screen capture).<br>Code: <a data-tooltip-position="top" aria-label="https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-MPL-2.0" rel="noopener nofollow" class="external-link is-unresolved" href="https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-MPL-2.0" target="_self">NON-AI MPL 2.0</a>
Artwork: — NO AI training. NO reproduction. NO exceptions.<br><img style="margin-left: 20px; margin-bottom: 20px;" align="center" src="https://github.com/Walker-Industries-RnD/Malicious-Affiliation-Ban/blob/main/WIBan.png?raw=true" referrerpolicy="no-referrer" target="_self" class="is-unresolved">
Unauthorized use of the artwork — including but not limited to copying, distribution, modification, or inclusion in any machine-learning training dataset — is strictly prohibited and will be prosecuted to the fullest extent of the law.
]]></description><link>1-start-here/1-welcome.html</link><guid isPermaLink="false">1 - Start Here/1 - Welcome.md</guid><pubDate>Mon, 27 Jul 2026 18:34:32 GMT</pubDate><enclosure url="." length="0" type="false"/><content:encoded>&lt;figure&gt;&lt;img src=&quot;.&quot;&gt;&lt;/figure&gt;</content:encoded></item><item><title><![CDATA[2 - Security Model and Threat Notes]]></title><description><![CDATA[Read this before this library guards anything that can hurt a user — camera, microphone, screen capture, files. It's honest about the edges.
The UI can't self-assert identity. Requester IDs come from XRUIOS.Manager, not from the calling window. An app is whoever the Manager says it is.
Per-app keys isolate stores. Each app's store is encrypted with a key the Manager generated for that app. Without the right key you can't read or write that app's permissions — GetVariable simply fails to decrypt. This is the real gate, more than the requester ID is.
Encrypted at rest, in RAM too. SecureData keeps keys/IDs/values AES-encrypted in memory and wipes them on dispose, so a memory scanner doesn't just find them lying around.
Fail closed. Missing store, wrong key, corrupt file → the answer is no.
Auditable denials. Handler.OnStoreError separates "denied" from "the store is broken/tampered." See <a data-href="1 - How Caching and Rate Limiting Work" href="3-extras/1-how-caching-and-rate-limiting-work.html" class="internal-link" target="_self" rel="noopener nofollow">1 - How Caching and Rate Limiting Work</a>.
No lost revocations under concurrency. Per-store locking means two simultaneous writes can't drop one.
Same-process code that holds the key can act as the app. This library authorizes based on the per-app key + requester ID. It cannot, by itself, prove a running process is the app it claims to be. If hostile code is executing inside the app's process with access to its key, it has the app's authority. Process/app attestation is the Manager's responsibility — this library assumes the Manager did that job when it issued the key.
Out-of-band store edits. If something can decrypt and rewrite the store file directly (i.e. it already has the per-app key), it can grant itself anything. Guard the key; guard the file location.
Revocation latency on cached grants. Going through the handler evicts the cache immediately. A grant changed outside the handler can linger up to the 5-minute TTL. For high-risk permissions, only mutate through the handler, and consider a shorter TTL.
Levels are not capabilities. permValue &gt;= requiredLevel models tiers on one permission name. It does not express independent capabilities. "Has camera but not mic" must be two permission names, not two levels on one — don't try to encode unrelated grants as a single number. Issue requester IDs and per-app keys only through XRUIOS.Manager. Never let a UI supply either.
<br>Put gated functions behind the Manager's dispatch so [RequiresPermission] is actually enforced (see <a data-href="3 - Auto-Gating Functions (The Attribute)" href="2-how-to-use/3-auto-gating-functions-(the-attribute).html" class="internal-link" target="_self" rel="noopener nofollow">3 - Auto-Gating Functions (The Attribute)</a>).
Use distinct permission names per capability ("Use Camera", "Use Microphone", "Edit Camera Footage"), and levels only for tiers within a capability.
Always mutate permissions through the handler so the cache is evicted — never hand-edit the store while the app is live.
Subscribe to Handler.OnStoreError and treat a store error on a security-critical path as an incident, not a shrug. This is a solid per-device permission store with correct caching, locking, and rate limiting. It becomes an OS-grade authority only because the XRUIOS.Manager sits underneath it issuing identities and keys. Keep that layer honest and the rest holds; weaken it and everything above it is decoration.
]]></description><link>3-extras/2-security-model-and-threat-notes.html</link><guid isPermaLink="false">3 - Extras/2 - Security Model and Threat Notes.md</guid><pubDate>Mon, 27 Jul 2026 18:32:19 GMT</pubDate></item><item><title><![CDATA[2 - Granting, Checking and Revoking]]></title><description><![CDATA[Once a store exists (see <a data-href="1 - Setting Up A Permission Store" href="2-how-to-use/1-setting-up-a-permission-store.html" class="internal-link" target="_self" rel="noopener nofollow">1 - Setting Up A Permission Store</a>), everything is done against a requester ID (the identifier XRUIOS.Manager issued to the app) and a permission name like "Edit Camera Footage".Permissions are integer levels, not just on/off. A grant stores a number; a check asks "is the stored number at least requiredLevel?" This lets you model tiers (e.g. 1 = view, 5 = edit, 10 = delete) on a single permission name.var requester = managerIssuedRequesterId; // SecureData from XRUIOS.Manager
var permName = "Edit Camera Footage".ToSecureData();
<br>The requester comes from the function's own inputs — the caller must point at or provide the ID. The permission name can be whatever you pass here, whether that's the function's own name or a friendlier label like "Edit Camera Footage". The auto-gating layer in <a data-href="3 - Auto-Gating Functions (The Attribute)" href="2-how-to-use/3-auto-gating-functions-(the-attribute).html" class="internal-link" target="_self" rel="noopener nofollow">3 - Auto-Gating Functions (The Attribute)</a> fills both of these in for you.The permission value is an int, wrapped in SecureData as its bytes:var level = new SecureData(BitConverter.GetBytes(5)); // grant at level 5 await handler.HandlePermission(saveDir, permClass, permName, requester, level, appKey);
<br>HandlePermission is an upsert, AKA if the requester doesn't exist yet it's added; if the permission already exists it's overwritten. After it writes, it refreshes the cache with the value it actually wrote (see <a data-href="1 - How Caching and Rate Limiting Work" href="3-extras/1-how-caching-and-rate-limiting-work.html" class="internal-link" target="_self" rel="noopener nofollow">1 - How Caching and Rate Limiting Work</a>), so the next check sees the new number immediately.bool canEdit = await handler.CheckPermission( saveDir, permClass, permName, requester, appKey, requiredLevel: 5); Stored level &gt;= requiredLevel → true.
Requester or permission absent → false.
Store missing / wrong key / corrupted → false (fail closed), and the error is pushed to Handler.OnStoreError so you can tell a real denial apart from a broken store.
requiredLevel defaults to 0. Calling CheckPermission(...) with no level asks "do they have this permission at all (&gt;= 0)?"The important part: because the cache stores the value, checking the same permission at a higher level later is compared honestly. A true at level 3 can not be replayed as a true at level 10.ChangePermission is a thin, clearly-named wrapper over `HandlePermission; it reads better at call sites where you're deliberately downgrading/upgrading:var newLevel = new SecureData(BitConverter.GetBytes(1)); // drop to level 1 await handler.ChangePermission(saveDir, permClass, permName, requester, appKey, newLevel);
The next CheckPermission reflects the new value right away — there's no stale "granted" left in the cache.await handler.RemovePermission(saveDir, permClass, permName, requester, appKey);
Throws KeyNotFoundException if the requester or that specific permission doesn't exist, and evicts just that one cache entry.Drops the app/requester and all its permissions in one go:await handler.RemoveRequester(saveDir, permClass, requester, appKey);
RemoveRequester does not take a permission name — it removes the whole requester. (Older code took an unused permission-name argument; it's gone.) It also precisely evicts every cache entry belonging to that requester, not the whole cache.]]></description><link>2-how-to-use/2-granting,-checking-and-revoking.html</link><guid isPermaLink="false">2 - How To Use/2 - Granting, Checking and Revoking.md</guid><pubDate>Mon, 27 Jul 2026 18:30:24 GMT</pubDate></item><item><title><![CDATA[1 - Setting Up A Permission Store]]></title><description><![CDATA[Every app gets its own permission store — a single encrypted file that holds every requester → permission → level mapping for that app. Before you can grant or check anything, that file has to exist.There are three pieces you'll pass around constantly, so let's name them once:The encryption key is not the user's main key. A manager should generate a key specifically for this app. If two apps shared a key, one could read and rewrite the other's permissions. One app, one key.Everything is wrapped in SecureData so it isn't sitting in plaintext:var saveDir = @"C:\XRUIOS\Permissions".ToSecureData();
var permClass = "CameraApp".ToSecureData();
var appKey = managerIssuedKey; // SecureData handed to you by XRUIOS.Manager
var handler = new Handler(); await handler.SetupPermFile(saveDir, permClass, appKey);
This creates CameraApp.json in your save directory, writes an encrypted Label and an empty permissions directory, and that's it. No dummy/placeholder requester is seeded — the store starts genuinely empty.SetupPermFile refuses to overwrite. If a store already exists at that path it throws a PermissionStoreException rather than wiping real permissions. That's on purpose.If you don't know whether the app has been set up yet:bool exists = await handler.PermissionFileExists(permClass, saveDir); if (!exists) await handler.SetupPermFile(saveDir, permClass, appKey);
Note the argument order: PermissionFileExists(PermissionClass, SaveDirectory). It looks up &lt;PermissionClass&gt;.json — the same file everything else reads. (Earlier builds checked the wrong filename; if you're updating old code, this is why a check might have always come back false.)One file per app, created once, keyed to that app. From here you actually put permissions in it — head to <a data-href="2 - Granting, Checking and Revoking" href="2-how-to-use/2-granting,-checking-and-revoking.html" class="internal-link" target="_self" rel="noopener nofollow">2 - Granting, Checking and Revoking</a>.]]></description><link>2-how-to-use/1-setting-up-a-permission-store.html</link><guid isPermaLink="false">2 - How To Use/1 - Setting Up A Permission Store.md</guid><pubDate>Mon, 27 Jul 2026 18:29:07 GMT</pubDate></item><item><title><![CDATA[XRUIOS Perms]]></title><description><![CDATA[<img src="assets/xruios-perms.png" target="_self">]]></description><link>assets/xruios-perms.html</link><guid isPermaLink="false">Assets/XRUIOS Perms.png</guid><pubDate>Mon, 27 Jul 2026 18:26:45 GMT</pubDate><enclosure url="." length="0" type="false"/><content:encoded>&lt;figure&gt;&lt;img src=&quot;.&quot;&gt;&lt;/figure&gt;</content:encoded></item><item><title><![CDATA[3 - Auto-Gating Functions (The Attribute)]]></title><description><![CDATA[Calling CheckPermission by hand in 400 functions is how you end up with 399 correct checks and one hole that owns the device. The point of the attribute is to declare the requirement once, on the function, and let the system enforce it before the body ever runs.Status: shipped. RequiresPermissionAttribute and PermissionEnforcer now exist in the PermissionHandler project (verified by smoke tests: allow-by-SecureData, allow-by-string, and deny-with-body-not-run). The attribute is still just a declaration — a bare attribute does nothing on its own. PermissionEnforcer is what reads it and runs the check, and the XRUIOS.Manager dispatch is what calls the enforcer. A ManagerStarterStub.cs scaffold is included to show the wiring; replace it with the real Manager.[RequiresPermission("Edit Camera Footage", Level = 5, RequesterArg = "requestId")]
public async Task TrimClip(string requestId, ClipRange range)
{ // If we got here, the requester is allowed at level &gt;= 5. // No manual CheckPermission needed.
}
Three things the attribute carries — all compile-time constants, which is the whole trick:The SecureData values (the key, the requester, the store path) are never put on the attribute. Attribute arguments must be compile-time constants — you physically cannot write a SecureData in [...]. The live values are resolved at call time: the requester from the named parameter, and the store + per-app key from the Manager's context for that app.
Requester ID → pulled from the method argument named by RequesterArg. The caller must point at or provide one — the system does not invent an identity. (This is the same "never trust the UI" rule from <a data-href="1 - Welcome" href="1-start-here/1-welcome.html" class="internal-link" target="_self" rel="noopener nofollow">1 - Welcome</a>: the ID had to be Manager-issued in the first place.)
Permission name → the attribute's first argument, or the method name if you leave it off. So "Edit Camera Footage" or just TrimClip.
Level → the attribute's Level.
Save directory + per-app key → the Manager already knows these for the app the call belongs to; the dispatch layer supplies them. The app code never handles the raw key.
The enforcement lives in PermissionEnforcer. The Manager builds a per-app context once and calls the enforcer at its dispatch boundary:var enforcer = new PermissionEnforcer(handler); // Per-app context the Manager owns (it issued the key):
var ctx = new AppPermissionContext(saveDir, permClass, appKey); // At the dispatch boundary — enforce, then invoke, in one call:
object? result = await enforcer.InvokeGuardedAsync(target, method, arguments, ctx);
// -&gt; throws PermissionDeniedException if the requester isn't allowed; the body never runs.
If you already have your own invoke path, just enforce and invoke yourself:await enforcer.EnforceAsync(method, arguments, ctx); // throws on denial
var result = method.Invoke(target, arguments);
Internally EnforceAsync:
reads [RequiresPermission] off the method (no attribute → nothing to enforce);
permName = attr.PermissionName ?? method.Name;
pulls the requester from the argument named attr.RequesterArg (a SecureData is used as-is; a string is copied into a SecureData without wiping your original);
calls handler.CheckPermission(..., attr.Level);
throws PermissionDeniedException if not allowed.
That's it. The function author writes one attribute; enforcement is uniform and lives in exactly one place.A null requester argument is treated as a denial, not a pass — you cannot authorize nobody. A misconfigured attribute (missing RequesterArg, or naming a parameter that doesn't exist) throws InvalidOperationException loudly rather than silently allowing.Because XRUIOS is an orchestration layer, calls into app functions already pass through the Manager's dispatch. That dispatch boundary is where the attribute is read via reflection and enforced — no compile-time weaving required. If you ever call a gated method directly (bypassing dispatch), the attribute is not enforced, so keep gated functions behind the Manager.If you later want gating on direct in-process calls too, that needs an aspect weaver (Metalama / Fody) rather than reflection — but for XRUIOS's dispatched model, reflection at the Manager boundary is the right fit and the cheapest.<br>See <a data-href="2 - Security Model and Threat Notes" href="3-extras/2-security-model-and-threat-notes.html" class="internal-link" target="_self" rel="noopener nofollow">2 - Security Model and Threat Notes</a> for what this does and doesn't protect against.]]></description><link>2-how-to-use/3-auto-gating-functions-(the-attribute).html</link><guid isPermaLink="false">2 - How To Use/3 - Auto-Gating Functions (The Attribute).md</guid><pubDate>Mon, 27 Jul 2026 17:31:49 GMT</pubDate></item><item><title><![CDATA[1 - How Caching and Rate Limiting Work]]></title><description><![CDATA[You don't need this page to use the library, but if you're wiring it into something sensitive (camera, screen capture) you should know how the fast paths behave.This is the single most important design choice, so it gets its own paragraph.The permission cache stores the integer permission value, not a true/false answer. Every cache hit is re-compared against the caller's requiredLevel:// on a hit:
return cachedValue != PERMISSION_ABSENT &amp;&amp; cachedValue &gt;= requiredLevel;
Why it matters: if we cached a decision, a check that passed at level 3 would cache true, and a later check at level 10 would get that true back — instant privilege escalation. By caching the value (5), level 3 → true and level 10 → false, both honest, from the same entry. It also means requiredLevel is not part of the cache key.Absence is negatively cached too, using a sentinel (PERMISSION_ABSENT), so repeatedly checking a requester that doesn't exist doesn't hammer the disk — but a sentinel is never mistaken for a real level.Cache entries live in a SecureCache (encrypted at rest, wiped on eviction) with a 5-minute TTL. Grants/changes/removals update or evict the relevant entry immediately, so a change is never hidden behind a stale hit.Revocation latency: a plain RemovePermission/ChangePermission does evict the cache, so it's immediate. But if a value were changed by editing the store file out-of-band, a cached grant could survive up to 5 minutes. For high-risk permissions, always go through the handler (which evicts), and consider a shorter TTL if you're paranoid. See <a data-href="2 - Security Model and Threat Notes" href="3-extras/2-security-model-and-threat-notes.html" class="internal-link" target="_self" rel="noopener nofollow">2 - Security Model and Threat Notes</a>.Every mutating call does load → mutate → save on the whole file. If two of those ran at once, one write would silently lose — and in a permission system a lost write is often a lost revocation.So each store file has its own lock (a SemaphoreSlim, keyed on the file path). Two writes to the same store queue; two writes to different stores run in parallel. Reads that fall through to disk take the same lock so nobody reads a half-written file.ChangePermission delegates to HandlePermission and does not take the lock itself — the lock isn't reentrant, so doing both would deadlock. If you add new mutating methods, take the lock once per top-level call.Each requester gets a bucket: 100 requests per 60 seconds.The window's expiry is stored alongside the count and is not pushed forward on every hit. That matters:
Wrong way (old behaviour): refresh the TTL every request → the window never ends for a busy caller → a legitimate, chatty app locks itself out permanently.
Right way (now): the count resets once the fixed 60-second window elapses. Busy-but-legit callers recover on their own.
The read-modify-write is done under a lock, so two concurrent calls can't both read the same count and overwrite each other.// exceeding the bucket:
throw new RateLimitExceededException(); // message contains "Rate limit exceeded"
<br>The bucket is keyed on the requester ID. Because those IDs are Manager-issued, not UI-chosen (see <a data-href="1 - Welcome" href="1-start-here/1-welcome.html" class="internal-link" target="_self" rel="noopener nofollow">1 - Welcome</a>), an app can't just rotate its ID to reset the bucket. This only holds as long as that stays true — if UIs could mint their own IDs, rate limiting (and everything else) would be bypassable.Reads fail closed (false), but a failure caused by a wrong key, a deleted store, or a corrupted file is not silently identical to an honest denial:Handler.OnStoreError = ex =&gt;
{ // log it, raise a device alert, whatever you need
};
Wire this up. A permission system that can't tell "denied" from "tampered" is unauditable, and unauditable is a euphemism for compromised.]]></description><link>3-extras/1-how-caching-and-rate-limiting-work.html</link><guid isPermaLink="false">3 - Extras/1 - How Caching and Rate Limiting Work.md</guid><pubDate>Mon, 27 Jul 2026 17:21:29 GMT</pubDate></item><item><title><![CDATA[WalkerDev]]></title><description><![CDATA[<img src="assets/walkerdev.png" target="_self">]]></description><link>assets/walkerdev.html</link><guid isPermaLink="false">Assets/WalkerDev.png</guid><pubDate>Mon, 02 Feb 2026 19:54:05 GMT</pubDate><enclosure url="assets/walkerdev.png" length="0" type="image/png"/><content:encoded>&lt;figure&gt;&lt;img src=&quot;assets/walkerdev.png&quot;&gt;&lt;/figure&gt;</content:encoded></item><item><title><![CDATA[Kennaness]]></title><description><![CDATA[<img src="assets/kennaness.png" target="_self">]]></description><link>assets/kennaness.html</link><guid isPermaLink="false">Assets/Kennaness.png</guid><pubDate>Mon, 02 Feb 2026 19:54:05 GMT</pubDate><enclosure url="assets/kennaness.png" length="0" type="image/png"/><content:encoded>&lt;figure&gt;&lt;img src=&quot;assets/kennaness.png&quot;&gt;&lt;/figure&gt;</content:encoded></item></channel></rss>