Open
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
38 changes: 31 additions & 7 deletions docs/docs/W3DS Protocol/Access-Control.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,18 +3,24 @@ sidebar_position: 7
---

# Access Control
We need a way to express simple access boundaries for data on our eVaults.
The eVault itself will guard the data from unwanted access, but also the platform that caches the data in its database will have to adhere to the same limitations.
We call a set of such limitations for a piece of data "policy".

An eVault record carries its own access rules. They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.
An eVault record carries its own access rules (policy). They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.

The rules live inside the record, not in a table beside it. Data syncs between platforms, and a central rules table would not follow it; the protection would be lost the moment the data moved. Keeping the policy in the record keeps it attached.

## Why the second half exists
With that said, we want to support acl by reference or inheritting one

## Why the second half exists (this is a very confusing title)

Naming every acceptable platform by hand does not scale, and it misses what an owner actually wants to say: *any platform may read this, provided it is reputable enough*. So a policy has two halves. Grants and denials name parties. The **Resource Link Ontology** sets quality conditions that admit a platform that was never named individually — and refuse one that was.

## Parties

Every party is an eName, `@<uuid>`. The same form identifies a user, a platform, or a group; a group stands for the set of its members and is resolved to them at check time. Ontologies are parties too and carry their own eName.
However, ontology eName may not be used inside a "grant".

```
@7b9c2e1a-4f30-4c5e-9a21-d8e0f1a2b3c4 a user
Expand All@@ -41,7 +47,7 @@ A write that sets a reserved bit is rejected rather than quietly narrowed, so a

## Grants

A record carries a list of grants, each naming one party and the permissions it holds.
Every record contains an `_acl` field with a list of grants, each naming one party and the permissions it holds.

Where several grants could apply to the same request, **the most specific one wins**: a grant to a user beats one to a platform, which beats one to a group the party belongs to. Only that grant is used. Less specific grants do not add to it.

Expand All@@ -52,7 +58,12 @@ grants: [
]
```

That member has **READ only**. The direct grant is more specific, so the group's UPDATE is never consulted for them.
What about the owner of the eVault? Do we need to add the owner to grants explicitly every time (unless we want to forbid her to change)?

That member has **READ only**.
The direct grant is more specific, so the group's UPDATE is never consulted for them.
By direct grant we refer to an eName of a person.
Specific may require a formal definition above or below.

Grants tied at the same specificity — duplicates, or two groups the party belongs to — are unioned. Nothing orders one above the other, and picking arbitrarily would make the outcome depend on storage order.

Expand DownExpand Up@@ -88,12 +99,16 @@ So a group whose record cannot be read is safe in both directions: it hands out

Where no group resolver is configured at all, groups simply do not resolve — that is the feature switched off rather than a failed lookup, and group grants and denials alike match nobody.

Do we have a position on eVault and platform implementers caching group membership? E.g., if we encourage, we should have it in our reference implementation. If we consider it bad, we should say so and explain why.

## Denials

A denial removes access regardless of any grant. **Deny always wins**, with no exceptions — it is the one place where specificity does not decide the outcome.

A denial names a party, or states a condition. A denial by name matches the party itself, the platform carrying its request, or any group it belongs to. A denial by condition applies to anyone who **fails** the check.

A policy may include a list of denied eNames and a list of denial conditions. The former are at `denials.ename` field and the latter is at `require` field.

```
grants: [ { ename: @2d4f…4a5b, perms: 0x01 } ]
denials.enames: [ @2d4f…4a5b ]
Expand All@@ -114,7 +129,7 @@ Operators are numeric only: `>=`, `>`, `<=`, `<`, `==`. The score is held on the
A path that is missing, resolves to several nodes, or resolves to a non-numeric value is a **failed** condition — never a passing one. A condition never fails open.

## Combining conditions

The word "group" here is confusing, because we just talked about groups of people. I would suggest "conjunction" instead.
Conditions are organised into groups. Within a group all conditions must pass; across groups any one group passing is enough. It is an OR of ANDs.

```
Expand All@@ -123,9 +138,12 @@ require: [
[ { @erep, $.score, >=, 90 } ] // Group B
]
```
I don't like that in the example you use the shortened version.
From paragraph above I'd expect a `{ontology: @sec, path: $.score, op: >=, value: 60}`

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own. Groups are evaluated in order and the first passing group decides.

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own.
Groups are evaluated in order and the first passing group decides.
We need to explain what "clearing security" means...
An empty group is an AND over zero conditions, so it always passes — that is how a policy says "admit anyone, subject to the denials".

## How a decision is reached
Expand All@@ -136,6 +154,8 @@ For a party **P** requesting action **A** on record **R**:
2. **A direct grant.** If P is named, take the single most specific applicable grant and allow only if its bitmask includes A. A grant decides the outcome on its own; step 3 is not reached.
3. **The ontology.** If at least one group in `require` passes for P, allow if `default_perms` includes A. Otherwise refuse.

At this point it is not clear if "ontology", "denial condition", "requires" -- mean the same thing.

```
grants: { @platform-2d4f: 0x01 }
denials.enames: [ @platform-bad1 ]
Expand DownExpand Up@@ -216,6 +236,8 @@ What comes back is always the policy **actually in force**. A record carrying on

Because the policy is readable by any permitted reader, treat its contents as visible to them: a denial names the parties an owner has excluded, and the grant list names who else holds access.

I.e., in the current model there is no way to hide from reading platforms the identity of people in allow or deny lists.

## Relationship to the legacy `acl` array

The older `acl: ["*"]` array still works and is unchanged. Where a record has no `_acl`, the array is read as before. Where a record has one, **`_acl` is authoritative and the array is ignored**.
Expand DownExpand Up@@ -262,6 +284,8 @@ Condition errors name their position — `require[0][1]`, `denials.conditions[0]

Read liberally when already stored: an unparseable grant or condition is dropped, and reserved bits are masked off, so a record written by a future version or corrupted in place stays readable rather than becoming inaccessible.

Just making sure: a platform operating on behalf itself, can modify any record on any eVault, so it can also modify any acl on any eVault?

### Versioning

`v` is the policy format version, and `1` is the only value this eVault accepts. A block declaring anything else is rejected rather than interpreted, so a policy written against a later format is never enforced as though it were version 1. Omitting `v` is treated as `1`.
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
Open
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
38 changes: 31 additions & 7 deletions docs/docs/W3DS Protocol/Access-Control.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,18 +3,24 @@ sidebar_position: 7
---

# Access Control
We need a way to express simple access boundaries for data on our eVaults.
The eVault itself will guard the data from unwanted access, but also the platform that caches the data in its database will have to adhere to the same limitations.
We call a set of such limitations for a piece of data "policy".

An eVault record carries its own access rules. They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.
An eVault record carries its own access rules (policy). They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.

The rules live inside the record, not in a table beside it. Data syncs between platforms, and a central rules table would not follow it; the protection would be lost the moment the data moved. Keeping the policy in the record keeps it attached.

## Why the second half exists
With that said, we want to support acl by reference or inheritting one

## Why the second half exists (this is a very confusing title)

Naming every acceptable platform by hand does not scale, and it misses what an owner actually wants to say: *any platform may read this, provided it is reputable enough*. So a policy has two halves. Grants and denials name parties. The **Resource Link Ontology** sets quality conditions that admit a platform that was never named individually — and refuse one that was.

## Parties

Every party is an eName, `@<uuid>`. The same form identifies a user, a platform, or a group; a group stands for the set of its members and is resolved to them at check time. Ontologies are parties too and carry their own eName.
However, ontology eName may not be used inside a "grant".

```
@7b9c2e1a-4f30-4c5e-9a21-d8e0f1a2b3c4 a user
Expand All@@ -41,7 +47,7 @@ A write that sets a reserved bit is rejected rather than quietly narrowed, so a

## Grants

A record carries a list of grants, each naming one party and the permissions it holds.
Every record contains an `_acl` field with a list of grants, each naming one party and the permissions it holds.

Where several grants could apply to the same request, **the most specific one wins**: a grant to a user beats one to a platform, which beats one to a group the party belongs to. Only that grant is used. Less specific grants do not add to it.

Expand All@@ -52,7 +58,12 @@ grants: [
]
```

That member has **READ only**. The direct grant is more specific, so the group's UPDATE is never consulted for them.
What about the owner of the eVault? Do we need to add the owner to grants explicitly every time (unless we want to forbid her to change)?

That member has **READ only**.
The direct grant is more specific, so the group's UPDATE is never consulted for them.
By direct grant we refer to an eName of a person.
Specific may require a formal definition above or below.

Grants tied at the same specificity — duplicates, or two groups the party belongs to — are unioned. Nothing orders one above the other, and picking arbitrarily would make the outcome depend on storage order.

Expand DownExpand Up@@ -88,12 +99,16 @@ So a group whose record cannot be read is safe in both directions: it hands out

Where no group resolver is configured at all, groups simply do not resolve — that is the feature switched off rather than a failed lookup, and group grants and denials alike match nobody.

Do we have a position on eVault and platform implementers caching group membership? E.g., if we encourage, we should have it in our reference implementation. If we consider it bad, we should say so and explain why.

## Denials

A denial removes access regardless of any grant. **Deny always wins**, with no exceptions — it is the one place where specificity does not decide the outcome.

A denial names a party, or states a condition. A denial by name matches the party itself, the platform carrying its request, or any group it belongs to. A denial by condition applies to anyone who **fails** the check.

A policy may include a list of denied eNames and a list of denial conditions. The former are at `denials.ename` field and the latter is at `require` field.

```
grants: [ { ename: @2d4f…4a5b, perms: 0x01 } ]
denials.enames: [ @2d4f…4a5b ]
Expand All@@ -114,7 +129,7 @@ Operators are numeric only: `>=`, `>`, `<=`, `<`, `==`. The score is held on the
A path that is missing, resolves to several nodes, or resolves to a non-numeric value is a **failed** condition — never a passing one. A condition never fails open.

## Combining conditions

The word "group" here is confusing, because we just talked about groups of people. I would suggest "conjunction" instead.
Conditions are organised into groups. Within a group all conditions must pass; across groups any one group passing is enough. It is an OR of ANDs.

```
Expand All@@ -123,9 +138,12 @@ require: [
[ { @erep, $.score, >=, 90 } ] // Group B
]
```
I don't like that in the example you use the shortened version.
From paragraph above I'd expect a `{ontology: @sec, path: $.score, op: >=, value: 60}`

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own. Groups are evaluated in order and the first passing group decides.

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own.
Groups are evaluated in order and the first passing group decides.
We need to explain what "clearing security" means...
An empty group is an AND over zero conditions, so it always passes — that is how a policy says "admit anyone, subject to the denials".

## How a decision is reached
Expand All@@ -136,6 +154,8 @@ For a party **P** requesting action **A** on record **R**:
2. **A direct grant.** If P is named, take the single most specific applicable grant and allow only if its bitmask includes A. A grant decides the outcome on its own; step 3 is not reached.
3. **The ontology.** If at least one group in `require` passes for P, allow if `default_perms` includes A. Otherwise refuse.

At this point it is not clear if "ontology", "denial condition", "requires" -- mean the same thing.

```
grants: { @platform-2d4f: 0x01 }
denials.enames: [ @platform-bad1 ]
Expand DownExpand Up@@ -216,6 +236,8 @@ What comes back is always the policy **actually in force**. A record carrying on

Because the policy is readable by any permitted reader, treat its contents as visible to them: a denial names the parties an owner has excluded, and the grant list names who else holds access.

I.e., in the current model there is no way to hide from reading platforms the identity of people in allow or deny lists.

## Relationship to the legacy `acl` array

The older `acl: ["*"]` array still works and is unchanged. Where a record has no `_acl`, the array is read as before. Where a record has one, **`_acl` is authoritative and the array is ignored**.
Expand DownExpand Up@@ -262,6 +284,8 @@ Condition errors name their position — `require[0][1]`, `denials.conditions[0]

Read liberally when already stored: an unparseable grant or condition is dropped, and reserved bits are masked off, so a record written by a future version or corrupted in place stays readable rather than becoming inaccessible.

Just making sure: a platform operating on behalf itself, can modify any record on any eVault, so it can also modify any acl on any eVault?

### Versioning

`v` is the policy format version, and `1` is the only value this eVault accepts. A block declaring anything else is rejected rather than interpreted, so a policy written against a later format is never enforced as though it were version 1. Omitting `v` is treated as `1`.
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
Open
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
38 changes: 31 additions & 7 deletions docs/docs/W3DS Protocol/Access-Control.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,18 +3,24 @@ sidebar_position: 7
---

# Access Control
We need a way to express simple access boundaries for data on our eVaults.
The eVault itself will guard the data from unwanted access, but also the platform that caches the data in its database will have to adhere to the same limitations.
We call a set of such limitations for a piece of data "policy".

An eVault record carries its own access rules. They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.
An eVault record carries its own access rules (policy). They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.

The rules live inside the record, not in a table beside it. Data syncs between platforms, and a central rules table would not follow it; the protection would be lost the moment the data moved. Keeping the policy in the record keeps it attached.

## Why the second half exists
With that said, we want to support acl by reference or inheritting one

## Why the second half exists (this is a very confusing title)

Naming every acceptable platform by hand does not scale, and it misses what an owner actually wants to say: *any platform may read this, provided it is reputable enough*. So a policy has two halves. Grants and denials name parties. The **Resource Link Ontology** sets quality conditions that admit a platform that was never named individually — and refuse one that was.

## Parties

Every party is an eName, `@<uuid>`. The same form identifies a user, a platform, or a group; a group stands for the set of its members and is resolved to them at check time. Ontologies are parties too and carry their own eName.
However, ontology eName may not be used inside a "grant".

```
@7b9c2e1a-4f30-4c5e-9a21-d8e0f1a2b3c4 a user
Expand All@@ -41,7 +47,7 @@ A write that sets a reserved bit is rejected rather than quietly narrowed, so a

## Grants

A record carries a list of grants, each naming one party and the permissions it holds.
Every record contains an `_acl` field with a list of grants, each naming one party and the permissions it holds.

Where several grants could apply to the same request, **the most specific one wins**: a grant to a user beats one to a platform, which beats one to a group the party belongs to. Only that grant is used. Less specific grants do not add to it.

Expand All@@ -52,7 +58,12 @@ grants: [
]
```

That member has **READ only**. The direct grant is more specific, so the group's UPDATE is never consulted for them.
What about the owner of the eVault? Do we need to add the owner to grants explicitly every time (unless we want to forbid her to change)?

That member has **READ only**.
The direct grant is more specific, so the group's UPDATE is never consulted for them.
By direct grant we refer to an eName of a person.
Specific may require a formal definition above or below.

Grants tied at the same specificity — duplicates, or two groups the party belongs to — are unioned. Nothing orders one above the other, and picking arbitrarily would make the outcome depend on storage order.

Expand DownExpand Up@@ -88,12 +99,16 @@ So a group whose record cannot be read is safe in both directions: it hands out

Where no group resolver is configured at all, groups simply do not resolve — that is the feature switched off rather than a failed lookup, and group grants and denials alike match nobody.

Do we have a position on eVault and platform implementers caching group membership? E.g., if we encourage, we should have it in our reference implementation. If we consider it bad, we should say so and explain why.

## Denials

A denial removes access regardless of any grant. **Deny always wins**, with no exceptions — it is the one place where specificity does not decide the outcome.

A denial names a party, or states a condition. A denial by name matches the party itself, the platform carrying its request, or any group it belongs to. A denial by condition applies to anyone who **fails** the check.

A policy may include a list of denied eNames and a list of denial conditions. The former are at `denials.ename` field and the latter is at `require` field.

```
grants: [ { ename: @2d4f…4a5b, perms: 0x01 } ]
denials.enames: [ @2d4f…4a5b ]
Expand All@@ -114,7 +129,7 @@ Operators are numeric only: `>=`, `>`, `<=`, `<`, `==`. The score is held on the
A path that is missing, resolves to several nodes, or resolves to a non-numeric value is a **failed** condition — never a passing one. A condition never fails open.

## Combining conditions

The word "group" here is confusing, because we just talked about groups of people. I would suggest "conjunction" instead.
Conditions are organised into groups. Within a group all conditions must pass; across groups any one group passing is enough. It is an OR of ANDs.

```
Expand All@@ -123,9 +138,12 @@ require: [
[ { @erep, $.score, >=, 90 } ] // Group B
]
```
I don't like that in the example you use the shortened version.
From paragraph above I'd expect a `{ontology: @sec, path: $.score, op: >=, value: 60}`

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own. Groups are evaluated in order and the first passing group decides.

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own.
Groups are evaluated in order and the first passing group decides.
We need to explain what "clearing security" means...
An empty group is an AND over zero conditions, so it always passes — that is how a policy says "admit anyone, subject to the denials".

## How a decision is reached
Expand All@@ -136,6 +154,8 @@ For a party **P** requesting action **A** on record **R**:
2. **A direct grant.** If P is named, take the single most specific applicable grant and allow only if its bitmask includes A. A grant decides the outcome on its own; step 3 is not reached.
3. **The ontology.** If at least one group in `require` passes for P, allow if `default_perms` includes A. Otherwise refuse.

At this point it is not clear if "ontology", "denial condition", "requires" -- mean the same thing.

```
grants: { @platform-2d4f: 0x01 }
denials.enames: [ @platform-bad1 ]
Expand DownExpand Up@@ -216,6 +236,8 @@ What comes back is always the policy **actually in force**. A record carrying on

Because the policy is readable by any permitted reader, treat its contents as visible to them: a denial names the parties an owner has excluded, and the grant list names who else holds access.

I.e., in the current model there is no way to hide from reading platforms the identity of people in allow or deny lists.

## Relationship to the legacy `acl` array

The older `acl: ["*"]` array still works and is unchanged. Where a record has no `_acl`, the array is read as before. Where a record has one, **`_acl` is authoritative and the array is ignored**.
Expand DownExpand Up@@ -262,6 +284,8 @@ Condition errors name their position — `require[0][1]`, `denials.conditions[0]

Read liberally when already stored: an unparseable grant or condition is dropped, and reserved bits are masked off, so a record written by a future version or corrupted in place stays readable rather than becoming inaccessible.

Just making sure: a platform operating on behalf itself, can modify any record on any eVault, so it can also modify any acl on any eVault?

### Versioning

`v` is the policy format version, and `1` is the only value this eVault accepts. A block declaring anything else is rejected rather than interpreted, so a policy written against a later format is never enforced as though it were version 1. Omitting `v` is treated as `1`.
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
Open
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
38 changes: 31 additions & 7 deletions docs/docs/W3DS Protocol/Access-Control.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,18 +3,24 @@ sidebar_position: 7
---

# Access Control
We need a way to express simple access boundaries for data on our eVaults.
The eVault itself will guard the data from unwanted access, but also the platform that caches the data in its database will have to adhere to the same limitations.
We call a set of such limitations for a piece of data "policy".

An eVault record carries its own access rules. They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.
An eVault record carries its own access rules (policy). They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.

The rules live inside the record, not in a table beside it. Data syncs between platforms, and a central rules table would not follow it; the protection would be lost the moment the data moved. Keeping the policy in the record keeps it attached.

## Why the second half exists
With that said, we want to support acl by reference or inheritting one

## Why the second half exists (this is a very confusing title)

Naming every acceptable platform by hand does not scale, and it misses what an owner actually wants to say: *any platform may read this, provided it is reputable enough*. So a policy has two halves. Grants and denials name parties. The **Resource Link Ontology** sets quality conditions that admit a platform that was never named individually — and refuse one that was.

## Parties

Every party is an eName, `@<uuid>`. The same form identifies a user, a platform, or a group; a group stands for the set of its members and is resolved to them at check time. Ontologies are parties too and carry their own eName.
However, ontology eName may not be used inside a "grant".

```
@7b9c2e1a-4f30-4c5e-9a21-d8e0f1a2b3c4 a user
Expand All@@ -41,7 +47,7 @@ A write that sets a reserved bit is rejected rather than quietly narrowed, so a

## Grants

A record carries a list of grants, each naming one party and the permissions it holds.
Every record contains an `_acl` field with a list of grants, each naming one party and the permissions it holds.

Where several grants could apply to the same request, **the most specific one wins**: a grant to a user beats one to a platform, which beats one to a group the party belongs to. Only that grant is used. Less specific grants do not add to it.

Expand All@@ -52,7 +58,12 @@ grants: [
]
```

That member has **READ only**. The direct grant is more specific, so the group's UPDATE is never consulted for them.
What about the owner of the eVault? Do we need to add the owner to grants explicitly every time (unless we want to forbid her to change)?

That member has **READ only**.
The direct grant is more specific, so the group's UPDATE is never consulted for them.
By direct grant we refer to an eName of a person.
Specific may require a formal definition above or below.

Grants tied at the same specificity — duplicates, or two groups the party belongs to — are unioned. Nothing orders one above the other, and picking arbitrarily would make the outcome depend on storage order.

Expand DownExpand Up@@ -88,12 +99,16 @@ So a group whose record cannot be read is safe in both directions: it hands out

Where no group resolver is configured at all, groups simply do not resolve — that is the feature switched off rather than a failed lookup, and group grants and denials alike match nobody.

Do we have a position on eVault and platform implementers caching group membership? E.g., if we encourage, we should have it in our reference implementation. If we consider it bad, we should say so and explain why.

## Denials

A denial removes access regardless of any grant. **Deny always wins**, with no exceptions — it is the one place where specificity does not decide the outcome.

A denial names a party, or states a condition. A denial by name matches the party itself, the platform carrying its request, or any group it belongs to. A denial by condition applies to anyone who **fails** the check.

A policy may include a list of denied eNames and a list of denial conditions. The former are at `denials.ename` field and the latter is at `require` field.

```
grants: [ { ename: @2d4f…4a5b, perms: 0x01 } ]
denials.enames: [ @2d4f…4a5b ]
Expand All@@ -114,7 +129,7 @@ Operators are numeric only: `>=`, `>`, `<=`, `<`, `==`. The score is held on the
A path that is missing, resolves to several nodes, or resolves to a non-numeric value is a **failed** condition — never a passing one. A condition never fails open.

## Combining conditions

The word "group" here is confusing, because we just talked about groups of people. I would suggest "conjunction" instead.
Conditions are organised into groups. Within a group all conditions must pass; across groups any one group passing is enough. It is an OR of ANDs.

```
Expand All@@ -123,9 +138,12 @@ require: [
[ { @erep, $.score, >=, 90 } ] // Group B
]
```
I don't like that in the example you use the shortened version.
From paragraph above I'd expect a `{ontology: @sec, path: $.score, op: >=, value: 60}`

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own. Groups are evaluated in order and the first passing group decides.

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own.
Groups are evaluated in order and the first passing group decides.
We need to explain what "clearing security" means...
An empty group is an AND over zero conditions, so it always passes — that is how a policy says "admit anyone, subject to the denials".

## How a decision is reached
Expand All@@ -136,6 +154,8 @@ For a party **P** requesting action **A** on record **R**:
2. **A direct grant.** If P is named, take the single most specific applicable grant and allow only if its bitmask includes A. A grant decides the outcome on its own; step 3 is not reached.
3. **The ontology.** If at least one group in `require` passes for P, allow if `default_perms` includes A. Otherwise refuse.

At this point it is not clear if "ontology", "denial condition", "requires" -- mean the same thing.

```
grants: { @platform-2d4f: 0x01 }
denials.enames: [ @platform-bad1 ]
Expand DownExpand Up@@ -216,6 +236,8 @@ What comes back is always the policy **actually in force**. A record carrying on

Because the policy is readable by any permitted reader, treat its contents as visible to them: a denial names the parties an owner has excluded, and the grant list names who else holds access.

I.e., in the current model there is no way to hide from reading platforms the identity of people in allow or deny lists.

## Relationship to the legacy `acl` array

The older `acl: ["*"]` array still works and is unchanged. Where a record has no `_acl`, the array is read as before. Where a record has one, **`_acl` is authoritative and the array is ignored**.
Expand DownExpand Up@@ -262,6 +284,8 @@ Condition errors name their position — `require[0][1]`, `denials.conditions[0]

Read liberally when already stored: an unparseable grant or condition is dropped, and reserved bits are masked off, so a record written by a future version or corrupted in place stays readable rather than becoming inaccessible.

Just making sure: a platform operating on behalf itself, can modify any record on any eVault, so it can also modify any acl on any eVault?

### Versioning

`v` is the policy format version, and `1` is the only value this eVault accepts. A block declaring anything else is rejected rather than interpreted, so a policy written against a later format is never enforced as though it were version 1. Omitting `v` is treated as `1`.
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
Open
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
38 changes: 31 additions & 7 deletions docs/docs/W3DS Protocol/Access-Control.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,18 +3,24 @@ sidebar_position: 7
---

# Access Control
We need a way to express simple access boundaries for data on our eVaults.
The eVault itself will guard the data from unwanted access, but also the platform that caches the data in its database will have to adhere to the same limitations.
We call a set of such limitations for a piece of data "policy".

An eVault record carries its own access rules. They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.
An eVault record carries its own access rules (policy). They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.

The rules live inside the record, not in a table beside it. Data syncs between platforms, and a central rules table would not follow it; the protection would be lost the moment the data moved. Keeping the policy in the record keeps it attached.

## Why the second half exists
With that said, we want to support acl by reference or inheritting one

## Why the second half exists (this is a very confusing title)

Naming every acceptable platform by hand does not scale, and it misses what an owner actually wants to say: *any platform may read this, provided it is reputable enough*. So a policy has two halves. Grants and denials name parties. The **Resource Link Ontology** sets quality conditions that admit a platform that was never named individually — and refuse one that was.

## Parties

Every party is an eName, `@<uuid>`. The same form identifies a user, a platform, or a group; a group stands for the set of its members and is resolved to them at check time. Ontologies are parties too and carry their own eName.
However, ontology eName may not be used inside a "grant".

```
@7b9c2e1a-4f30-4c5e-9a21-d8e0f1a2b3c4 a user
Expand All@@ -41,7 +47,7 @@ A write that sets a reserved bit is rejected rather than quietly narrowed, so a

## Grants

A record carries a list of grants, each naming one party and the permissions it holds.
Every record contains an `_acl` field with a list of grants, each naming one party and the permissions it holds.

Where several grants could apply to the same request, **the most specific one wins**: a grant to a user beats one to a platform, which beats one to a group the party belongs to. Only that grant is used. Less specific grants do not add to it.

Expand All@@ -52,7 +58,12 @@ grants: [
]
```

That member has **READ only**. The direct grant is more specific, so the group's UPDATE is never consulted for them.
What about the owner of the eVault? Do we need to add the owner to grants explicitly every time (unless we want to forbid her to change)?

That member has **READ only**.
The direct grant is more specific, so the group's UPDATE is never consulted for them.
By direct grant we refer to an eName of a person.
Specific may require a formal definition above or below.

Grants tied at the same specificity — duplicates, or two groups the party belongs to — are unioned. Nothing orders one above the other, and picking arbitrarily would make the outcome depend on storage order.

Expand DownExpand Up@@ -88,12 +99,16 @@ So a group whose record cannot be read is safe in both directions: it hands out

Where no group resolver is configured at all, groups simply do not resolve — that is the feature switched off rather than a failed lookup, and group grants and denials alike match nobody.

Do we have a position on eVault and platform implementers caching group membership? E.g., if we encourage, we should have it in our reference implementation. If we consider it bad, we should say so and explain why.

## Denials

A denial removes access regardless of any grant. **Deny always wins**, with no exceptions — it is the one place where specificity does not decide the outcome.

A denial names a party, or states a condition. A denial by name matches the party itself, the platform carrying its request, or any group it belongs to. A denial by condition applies to anyone who **fails** the check.

A policy may include a list of denied eNames and a list of denial conditions. The former are at `denials.ename` field and the latter is at `require` field.

```
grants: [ { ename: @2d4f…4a5b, perms: 0x01 } ]
denials.enames: [ @2d4f…4a5b ]
Expand All@@ -114,7 +129,7 @@ Operators are numeric only: `>=`, `>`, `<=`, `<`, `==`. The score is held on the
A path that is missing, resolves to several nodes, or resolves to a non-numeric value is a **failed** condition — never a passing one. A condition never fails open.

## Combining conditions

The word "group" here is confusing, because we just talked about groups of people. I would suggest "conjunction" instead.
Conditions are organised into groups. Within a group all conditions must pass; across groups any one group passing is enough. It is an OR of ANDs.

```
Expand All@@ -123,9 +138,12 @@ require: [
[ { @erep, $.score, >=, 90 } ] // Group B
]
```
I don't like that in the example you use the shortened version.
From paragraph above I'd expect a `{ontology: @sec, path: $.score, op: >=, value: 60}`

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own. Groups are evaluated in order and the first passing group decides.

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own.
Groups are evaluated in order and the first passing group decides.
We need to explain what "clearing security" means...
An empty group is an AND over zero conditions, so it always passes — that is how a policy says "admit anyone, subject to the denials".

## How a decision is reached
Expand All@@ -136,6 +154,8 @@ For a party **P** requesting action **A** on record **R**:
2. **A direct grant.** If P is named, take the single most specific applicable grant and allow only if its bitmask includes A. A grant decides the outcome on its own; step 3 is not reached.
3. **The ontology.** If at least one group in `require` passes for P, allow if `default_perms` includes A. Otherwise refuse.

At this point it is not clear if "ontology", "denial condition", "requires" -- mean the same thing.

```
grants: { @platform-2d4f: 0x01 }
denials.enames: [ @platform-bad1 ]
Expand DownExpand Up@@ -216,6 +236,8 @@ What comes back is always the policy **actually in force**. A record carrying on

Because the policy is readable by any permitted reader, treat its contents as visible to them: a denial names the parties an owner has excluded, and the grant list names who else holds access.

I.e., in the current model there is no way to hide from reading platforms the identity of people in allow or deny lists.

## Relationship to the legacy `acl` array

The older `acl: ["*"]` array still works and is unchanged. Where a record has no `_acl`, the array is read as before. Where a record has one, **`_acl` is authoritative and the array is ignored**.
Expand DownExpand Up@@ -262,6 +284,8 @@ Condition errors name their position — `require[0][1]`, `denials.conditions[0]

Read liberally when already stored: an unparseable grant or condition is dropped, and reserved bits are masked off, so a record written by a future version or corrupted in place stays readable rather than becoming inaccessible.

Just making sure: a platform operating on behalf itself, can modify any record on any eVault, so it can also modify any acl on any eVault?

### Versioning

`v` is the policy format version, and `1` is the only value this eVault accepts. A block declaring anything else is rejected rather than interpreted, so a policy written against a later format is never enforced as though it were version 1. Omitting `v` is treated as `1`.
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
Open
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
38 changes: 31 additions & 7 deletions docs/docs/W3DS Protocol/Access-Control.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,18 +3,24 @@ sidebar_position: 7
---

# Access Control
We need a way to express simple access boundaries for data on our eVaults.
The eVault itself will guard the data from unwanted access, but also the platform that caches the data in its database will have to adhere to the same limitations.
We call a set of such limitations for a piece of data "policy".

An eVault record carries its own access rules. They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.
An eVault record carries its own access rules (policy). They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.

The rules live inside the record, not in a table beside it. Data syncs between platforms, and a central rules table would not follow it; the protection would be lost the moment the data moved. Keeping the policy in the record keeps it attached.

## Why the second half exists
With that said, we want to support acl by reference or inheritting one

## Why the second half exists (this is a very confusing title)

Naming every acceptable platform by hand does not scale, and it misses what an owner actually wants to say: *any platform may read this, provided it is reputable enough*. So a policy has two halves. Grants and denials name parties. The **Resource Link Ontology** sets quality conditions that admit a platform that was never named individually — and refuse one that was.

## Parties

Every party is an eName, `@<uuid>`. The same form identifies a user, a platform, or a group; a group stands for the set of its members and is resolved to them at check time. Ontologies are parties too and carry their own eName.
However, ontology eName may not be used inside a "grant".

```
@7b9c2e1a-4f30-4c5e-9a21-d8e0f1a2b3c4 a user
Expand All@@ -41,7 +47,7 @@ A write that sets a reserved bit is rejected rather than quietly narrowed, so a

## Grants

A record carries a list of grants, each naming one party and the permissions it holds.
Every record contains an `_acl` field with a list of grants, each naming one party and the permissions it holds.

Where several grants could apply to the same request, **the most specific one wins**: a grant to a user beats one to a platform, which beats one to a group the party belongs to. Only that grant is used. Less specific grants do not add to it.

Expand All@@ -52,7 +58,12 @@ grants: [
]
```

That member has **READ only**. The direct grant is more specific, so the group's UPDATE is never consulted for them.
What about the owner of the eVault? Do we need to add the owner to grants explicitly every time (unless we want to forbid her to change)?

That member has **READ only**.
The direct grant is more specific, so the group's UPDATE is never consulted for them.
By direct grant we refer to an eName of a person.
Specific may require a formal definition above or below.

Grants tied at the same specificity — duplicates, or two groups the party belongs to — are unioned. Nothing orders one above the other, and picking arbitrarily would make the outcome depend on storage order.

Expand DownExpand Up@@ -88,12 +99,16 @@ So a group whose record cannot be read is safe in both directions: it hands out

Where no group resolver is configured at all, groups simply do not resolve — that is the feature switched off rather than a failed lookup, and group grants and denials alike match nobody.

Do we have a position on eVault and platform implementers caching group membership? E.g., if we encourage, we should have it in our reference implementation. If we consider it bad, we should say so and explain why.

## Denials

A denial removes access regardless of any grant. **Deny always wins**, with no exceptions — it is the one place where specificity does not decide the outcome.

A denial names a party, or states a condition. A denial by name matches the party itself, the platform carrying its request, or any group it belongs to. A denial by condition applies to anyone who **fails** the check.

A policy may include a list of denied eNames and a list of denial conditions. The former are at `denials.ename` field and the latter is at `require` field.

```
grants: [ { ename: @2d4f…4a5b, perms: 0x01 } ]
denials.enames: [ @2d4f…4a5b ]
Expand All@@ -114,7 +129,7 @@ Operators are numeric only: `>=`, `>`, `<=`, `<`, `==`. The score is held on the
A path that is missing, resolves to several nodes, or resolves to a non-numeric value is a **failed** condition — never a passing one. A condition never fails open.

## Combining conditions

The word "group" here is confusing, because we just talked about groups of people. I would suggest "conjunction" instead.
Conditions are organised into groups. Within a group all conditions must pass; across groups any one group passing is enough. It is an OR of ANDs.

```
Expand All@@ -123,9 +138,12 @@ require: [
[ { @erep, $.score, >=, 90 } ] // Group B
]
```
I don't like that in the example you use the shortened version.
From paragraph above I'd expect a `{ontology: @sec, path: $.score, op: >=, value: 60}`

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own. Groups are evaluated in order and the first passing group decides.

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own.
Groups are evaluated in order and the first passing group decides.
We need to explain what "clearing security" means...
An empty group is an AND over zero conditions, so it always passes — that is how a policy says "admit anyone, subject to the denials".

## How a decision is reached
Expand All@@ -136,6 +154,8 @@ For a party **P** requesting action **A** on record **R**:
2. **A direct grant.** If P is named, take the single most specific applicable grant and allow only if its bitmask includes A. A grant decides the outcome on its own; step 3 is not reached.
3. **The ontology.** If at least one group in `require` passes for P, allow if `default_perms` includes A. Otherwise refuse.

At this point it is not clear if "ontology", "denial condition", "requires" -- mean the same thing.

```
grants: { @platform-2d4f: 0x01 }
denials.enames: [ @platform-bad1 ]
Expand DownExpand Up@@ -216,6 +236,8 @@ What comes back is always the policy **actually in force**. A record carrying on

Because the policy is readable by any permitted reader, treat its contents as visible to them: a denial names the parties an owner has excluded, and the grant list names who else holds access.

I.e., in the current model there is no way to hide from reading platforms the identity of people in allow or deny lists.

## Relationship to the legacy `acl` array

The older `acl: ["*"]` array still works and is unchanged. Where a record has no `_acl`, the array is read as before. Where a record has one, **`_acl` is authoritative and the array is ignored**.
Expand DownExpand Up@@ -262,6 +284,8 @@ Condition errors name their position — `require[0][1]`, `denials.conditions[0]

Read liberally when already stored: an unparseable grant or condition is dropped, and reserved bits are masked off, so a record written by a future version or corrupted in place stays readable rather than becoming inaccessible.

Just making sure: a platform operating on behalf itself, can modify any record on any eVault, so it can also modify any acl on any eVault?

### Versioning

`v` is the policy format version, and `1` is the only value this eVault accepts. A block declaring anything else is rejected rather than interpreted, so a policy written against a later format is never enforced as though it were version 1. Omitting `v` is treated as `1`.
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
Open
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
38 changes: 31 additions & 7 deletions docs/docs/W3DS Protocol/Access-Control.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,18 +3,24 @@ sidebar_position: 7
---

# Access Control
We need a way to express simple access boundaries for data on our eVaults.
The eVault itself will guard the data from unwanted access, but also the platform that caches the data in its database will have to adhere to the same limitations.
We call a set of such limitations for a piece of data "policy".

An eVault record carries its own access rules. They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.
An eVault record carries its own access rules (policy). They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.

The rules live inside the record, not in a table beside it. Data syncs between platforms, and a central rules table would not follow it; the protection would be lost the moment the data moved. Keeping the policy in the record keeps it attached.

## Why the second half exists
With that said, we want to support acl by reference or inheritting one

## Why the second half exists (this is a very confusing title)

Naming every acceptable platform by hand does not scale, and it misses what an owner actually wants to say: *any platform may read this, provided it is reputable enough*. So a policy has two halves. Grants and denials name parties. The **Resource Link Ontology** sets quality conditions that admit a platform that was never named individually — and refuse one that was.

## Parties

Every party is an eName, `@<uuid>`. The same form identifies a user, a platform, or a group; a group stands for the set of its members and is resolved to them at check time. Ontologies are parties too and carry their own eName.
However, ontology eName may not be used inside a "grant".

```
@7b9c2e1a-4f30-4c5e-9a21-d8e0f1a2b3c4 a user
Expand All@@ -41,7 +47,7 @@ A write that sets a reserved bit is rejected rather than quietly narrowed, so a

## Grants

A record carries a list of grants, each naming one party and the permissions it holds.
Every record contains an `_acl` field with a list of grants, each naming one party and the permissions it holds.

Where several grants could apply to the same request, **the most specific one wins**: a grant to a user beats one to a platform, which beats one to a group the party belongs to. Only that grant is used. Less specific grants do not add to it.

Expand All@@ -52,7 +58,12 @@ grants: [
]
```

That member has **READ only**. The direct grant is more specific, so the group's UPDATE is never consulted for them.
What about the owner of the eVault? Do we need to add the owner to grants explicitly every time (unless we want to forbid her to change)?

That member has **READ only**.
The direct grant is more specific, so the group's UPDATE is never consulted for them.
By direct grant we refer to an eName of a person.
Specific may require a formal definition above or below.

Grants tied at the same specificity — duplicates, or two groups the party belongs to — are unioned. Nothing orders one above the other, and picking arbitrarily would make the outcome depend on storage order.

Expand DownExpand Up@@ -88,12 +99,16 @@ So a group whose record cannot be read is safe in both directions: it hands out

Where no group resolver is configured at all, groups simply do not resolve — that is the feature switched off rather than a failed lookup, and group grants and denials alike match nobody.

Do we have a position on eVault and platform implementers caching group membership? E.g., if we encourage, we should have it in our reference implementation. If we consider it bad, we should say so and explain why.

## Denials

A denial removes access regardless of any grant. **Deny always wins**, with no exceptions — it is the one place where specificity does not decide the outcome.

A denial names a party, or states a condition. A denial by name matches the party itself, the platform carrying its request, or any group it belongs to. A denial by condition applies to anyone who **fails** the check.

A policy may include a list of denied eNames and a list of denial conditions. The former are at `denials.ename` field and the latter is at `require` field.

```
grants: [ { ename: @2d4f…4a5b, perms: 0x01 } ]
denials.enames: [ @2d4f…4a5b ]
Expand All@@ -114,7 +129,7 @@ Operators are numeric only: `>=`, `>`, `<=`, `<`, `==`. The score is held on the
A path that is missing, resolves to several nodes, or resolves to a non-numeric value is a **failed** condition — never a passing one. A condition never fails open.

## Combining conditions

The word "group" here is confusing, because we just talked about groups of people. I would suggest "conjunction" instead.
Conditions are organised into groups. Within a group all conditions must pass; across groups any one group passing is enough. It is an OR of ANDs.

```
Expand All@@ -123,9 +138,12 @@ require: [
[ { @erep, $.score, >=, 90 } ] // Group B
]
```
I don't like that in the example you use the shortened version.
From paragraph above I'd expect a `{ontology: @sec, path: $.score, op: >=, value: 60}`

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own. Groups are evaluated in order and the first passing group decides.

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own.
Groups are evaluated in order and the first passing group decides.
We need to explain what "clearing security" means...
An empty group is an AND over zero conditions, so it always passes — that is how a policy says "admit anyone, subject to the denials".

## How a decision is reached
Expand All@@ -136,6 +154,8 @@ For a party **P** requesting action **A** on record **R**:
2. **A direct grant.** If P is named, take the single most specific applicable grant and allow only if its bitmask includes A. A grant decides the outcome on its own; step 3 is not reached.
3. **The ontology.** If at least one group in `require` passes for P, allow if `default_perms` includes A. Otherwise refuse.

At this point it is not clear if "ontology", "denial condition", "requires" -- mean the same thing.

```
grants: { @platform-2d4f: 0x01 }
denials.enames: [ @platform-bad1 ]
Expand DownExpand Up@@ -216,6 +236,8 @@ What comes back is always the policy **actually in force**. A record carrying on

Because the policy is readable by any permitted reader, treat its contents as visible to them: a denial names the parties an owner has excluded, and the grant list names who else holds access.

I.e., in the current model there is no way to hide from reading platforms the identity of people in allow or deny lists.

## Relationship to the legacy `acl` array

The older `acl: ["*"]` array still works and is unchanged. Where a record has no `_acl`, the array is read as before. Where a record has one, **`_acl` is authoritative and the array is ignored**.
Expand DownExpand Up@@ -262,6 +284,8 @@ Condition errors name their position — `require[0][1]`, `denials.conditions[0]

Read liberally when already stored: an unparseable grant or condition is dropped, and reserved bits are masked off, so a record written by a future version or corrupted in place stays readable rather than becoming inaccessible.

Just making sure: a platform operating on behalf itself, can modify any record on any eVault, so it can also modify any acl on any eVault?

### Versioning

`v` is the policy format version, and `1` is the only value this eVault accepts. A block declaring anything else is rejected rather than interpreted, so a policy written against a later format is never enforced as though it were version 1. Omitting `v` is treated as `1`.
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
Open
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
38 changes: 31 additions & 7 deletions docs/docs/W3DS Protocol/Access-Control.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,18 +3,24 @@ sidebar_position: 7
---

# Access Control
We need a way to express simple access boundaries for data on our eVaults.
The eVault itself will guard the data from unwanted access, but also the platform that caches the data in its database will have to adhere to the same limitations.
We call a set of such limitations for a piece of data "policy".

An eVault record carries its own access rules. They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.
An eVault record carries its own access rules (policy). They say which parties may read it, add to it, change it, or delete it — and, separately, what a platform must *be* before it may read at all.

The rules live inside the record, not in a table beside it. Data syncs between platforms, and a central rules table would not follow it; the protection would be lost the moment the data moved. Keeping the policy in the record keeps it attached.

## Why the second half exists
With that said, we want to support acl by reference or inheritting one

## Why the second half exists (this is a very confusing title)

Naming every acceptable platform by hand does not scale, and it misses what an owner actually wants to say: *any platform may read this, provided it is reputable enough*. So a policy has two halves. Grants and denials name parties. The **Resource Link Ontology** sets quality conditions that admit a platform that was never named individually — and refuse one that was.

## Parties

Every party is an eName, `@<uuid>`. The same form identifies a user, a platform, or a group; a group stands for the set of its members and is resolved to them at check time. Ontologies are parties too and carry their own eName.
However, ontology eName may not be used inside a "grant".

```
@7b9c2e1a-4f30-4c5e-9a21-d8e0f1a2b3c4 a user
Expand All@@ -41,7 +47,7 @@ A write that sets a reserved bit is rejected rather than quietly narrowed, so a

## Grants

A record carries a list of grants, each naming one party and the permissions it holds.
Every record contains an `_acl` field with a list of grants, each naming one party and the permissions it holds.

Where several grants could apply to the same request, **the most specific one wins**: a grant to a user beats one to a platform, which beats one to a group the party belongs to. Only that grant is used. Less specific grants do not add to it.

Expand All@@ -52,7 +58,12 @@ grants: [
]
```

That member has **READ only**. The direct grant is more specific, so the group's UPDATE is never consulted for them.
What about the owner of the eVault? Do we need to add the owner to grants explicitly every time (unless we want to forbid her to change)?

That member has **READ only**.
The direct grant is more specific, so the group's UPDATE is never consulted for them.
By direct grant we refer to an eName of a person.
Specific may require a formal definition above or below.

Grants tied at the same specificity — duplicates, or two groups the party belongs to — are unioned. Nothing orders one above the other, and picking arbitrarily would make the outcome depend on storage order.

Expand DownExpand Up@@ -88,12 +99,16 @@ So a group whose record cannot be read is safe in both directions: it hands out

Where no group resolver is configured at all, groups simply do not resolve — that is the feature switched off rather than a failed lookup, and group grants and denials alike match nobody.

Do we have a position on eVault and platform implementers caching group membership? E.g., if we encourage, we should have it in our reference implementation. If we consider it bad, we should say so and explain why.

## Denials

A denial removes access regardless of any grant. **Deny always wins**, with no exceptions — it is the one place where specificity does not decide the outcome.

A denial names a party, or states a condition. A denial by name matches the party itself, the platform carrying its request, or any group it belongs to. A denial by condition applies to anyone who **fails** the check.

A policy may include a list of denied eNames and a list of denial conditions. The former are at `denials.ename` field and the latter is at `require` field.

```
grants: [ { ename: @2d4f…4a5b, perms: 0x01 } ]
denials.enames: [ @2d4f…4a5b ]
Expand All@@ -114,7 +129,7 @@ Operators are numeric only: `>=`, `>`, `<=`, `<`, `==`. The score is held on the
A path that is missing, resolves to several nodes, or resolves to a non-numeric value is a **failed** condition — never a passing one. A condition never fails open.

## Combining conditions

The word "group" here is confusing, because we just talked about groups of people. I would suggest "conjunction" instead.
Conditions are organised into groups. Within a group all conditions must pass; across groups any one group passing is enough. It is an OR of ANDs.

```
Expand All@@ -123,9 +138,12 @@ require: [
[ { @erep, $.score, >=, 90 } ] // Group B
]
```
I don't like that in the example you use the shortened version.
From paragraph above I'd expect a `{ontology: @sec, path: $.score, op: >=, value: 60}`

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own. Groups are evaluated in order and the first passing group decides.

A platform is admitted if it clears security *and* reputation together, or clears a higher reputation bar on its own.
Groups are evaluated in order and the first passing group decides.
We need to explain what "clearing security" means...
An empty group is an AND over zero conditions, so it always passes — that is how a policy says "admit anyone, subject to the denials".

## How a decision is reached
Expand All@@ -136,6 +154,8 @@ For a party **P** requesting action **A** on record **R**:
2. **A direct grant.** If P is named, take the single most specific applicable grant and allow only if its bitmask includes A. A grant decides the outcome on its own; step 3 is not reached.
3. **The ontology.** If at least one group in `require` passes for P, allow if `default_perms` includes A. Otherwise refuse.

At this point it is not clear if "ontology", "denial condition", "requires" -- mean the same thing.

```
grants: { @platform-2d4f: 0x01 }
denials.enames: [ @platform-bad1 ]
Expand DownExpand Up@@ -216,6 +236,8 @@ What comes back is always the policy **actually in force**. A record carrying on

Because the policy is readable by any permitted reader, treat its contents as visible to them: a denial names the parties an owner has excluded, and the grant list names who else holds access.

I.e., in the current model there is no way to hide from reading platforms the identity of people in allow or deny lists.

## Relationship to the legacy `acl` array

The older `acl: ["*"]` array still works and is unchanged. Where a record has no `_acl`, the array is read as before. Where a record has one, **`_acl` is authoritative and the array is ignored**.
Expand DownExpand Up@@ -262,6 +284,8 @@ Condition errors name their position — `require[0][1]`, `denials.conditions[0]

Read liberally when already stored: an unparseable grant or condition is dropped, and reserved bits are masked off, so a record written by a future version or corrupted in place stays readable rather than becoming inaccessible.

Just making sure: a platform operating on behalf itself, can modify any record on any eVault, so it can also modify any acl on any eVault?

### Versioning

`v` is the policy format version, and `1` is the only value this eVault accepts. A block declaring anything else is rejected rather than interpreted, so a policy written against a later format is never enforced as though it were version 1. Omitting `v` is treated as `1`.
Expand Down
Loading