Skip to content

Commit 957dd5d

Browse files
committed
docs: RFC 0012 — JIT account-access permission for ProductAccountId methods
Introduces a per-account just-in-time permission check for all Host API methods that accept a ProductAccountId, preventing cross-product identity leakage. Closes #134.
1 parent 46cad7c commit 957dd5d

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
---
2+
title: "JIT Account-Access Permission for ProductAccountId Methods"
3+
owner: "@filvecchiato"
4+
---
5+
6+
# RFC 0012 — JIT Account-Access Permission for ProductAccountId Methods
7+
8+
## Summary
9+
10+
This RFC introduces a per-account just-in-time (JIT) permission check for all Host API methods that accept a `ProductAccountId`. Today, any product can call `host_account_get`, `host_account_get_alias`, `host_account_create_proof`, signing methods, `host_create_transaction`, `remote_statement_store_create_proof`, and `host_payment_top_up` (via the `ProductAccount` source variant) with an arbitrary `ProductAccountId` — including identifiers belonging to other products — without user awareness or consent. This RFC requires the host to obtain explicit user approval before granting access to a specific `ProductAccountId`, preventing cross-product identity leakage.
11+
12+
## Motivation
13+
14+
`ProductAccountId` is a `(DotNsIdentifier, DerivationIndex)` tuple. The `DotNsIdentifier` component is a product's registered DotNS name, and the `DerivationIndex` selects a specific derived account under that product's namespace. The host derives cryptographic keys, aliases, and proofs from this identifier — all of which are sensitive, user-specific material scoped to a product domain.
15+
16+
Currently, none of the methods that accept `ProductAccountId` enforce that the calling product is authorized to access the requested domain. A malicious product can:
17+
18+
1. Call `host_account_get(["legitimate-product.dot", 0])` to obtain the user's derived public key for another product's domain.
19+
2. Call `host_account_get_alias` to learn the user's contextual alias in another product's ring VRF context.
20+
3. Call `host_account_create_proof` to generate valid ring VRF proofs under another product's identity.
21+
4. Call `host_sign_raw` or `host_sign_payload` with another product's account to produce signatures.
22+
5. Call `host_create_transaction` to create signed transactions using another product's derived key.
23+
6. Call `remote_statement_store_create_proof` to create statement proofs under another product's account.
24+
25+
This enables cross-product user tracking and identity correlation without the user's knowledge — the same class of privacy concern that motivated the JIT permission model for `host_account_get_root` (RFC-0010).
26+
27+
## Detailed Design
28+
29+
### Permission model
30+
31+
The host MUST maintain a per-product, per-`ProductAccountId` permission grant. When a product calls any method that includes a `ProductAccountId` (either as a direct parameter or embedded in a request struct), the host MUST check whether the calling product has been granted access to that specific `ProductAccountId`.
32+
33+
The permission key is the full `ProductAccountId` tuple `(DotNsIdentifier, DerivationIndex)`. A grant for `("example.dot", 0)` does NOT extend to `("example.dot", 1)`.
34+
35+
### Permission lifecycle
36+
37+
1. **First call** — When a product calls a `ProductAccountId`-bearing method for the first time with a given `ProductAccountId`, the host presents an approval dialog to the user. The dialog MUST identify the requesting product and the target `ProductAccountId` (at minimum the `DotNsIdentifier`).
38+
2. **Approved** — The host caches the grant and proceeds with the method. Subsequent calls from the same product with the same `ProductAccountId` resolve immediately without prompting.
39+
3. **Denied** — The method returns its domain-specific rejection error:
40+
- `host_account_get`, `host_account_get_alias`: `RequestCredentialsErr::Rejected`
41+
- `host_account_create_proof`: `CreateProofErr::Rejected`
42+
- `host_sign_raw`, `host_sign_payload`: `SigningErr::PermissionDenied`
43+
- `host_create_transaction`: `CreateTransactionErr::PermissionDenied`
44+
- `remote_statement_store_create_proof`: `StatementProofErr::Rejected`
45+
- `host_payment_top_up` (with `PaymentTopUpSource::ProductAccount`): `PaymentTopUpErr::Rejected`
46+
4. **Grant persistence** — The host SHOULD persist grants across sessions for the same product identity. Session-scoped grants are acceptable as a minimum conforming implementation.
47+
48+
### Same-domain optimization
49+
50+
When the calling product's own `DotNsIdentifier` matches the `DotNsIdentifier` in the `ProductAccountId`, the host MAY skip the permission prompt and grant access implicitly. This is the common case: a product accessing its own derived accounts. The host MUST still prompt when the `DotNsIdentifier` differs from the calling product's identity.
51+
52+
Hosts that cannot reliably determine the calling product's identity (e.g. during development or in permissive sandbox modes) MUST fall back to prompting for all `ProductAccountId` requests.
53+
54+
### Affected methods
55+
56+
| Method | `ProductAccountId` location | Rejection error |
57+
|--------|---------------------------|-----------------|
58+
| `host_account_get` | Direct parameter | `RequestCredentialsErr::Rejected` |
59+
| `host_account_get_alias` | Direct parameter | `RequestCredentialsErr::Rejected` |
60+
| `host_account_create_proof` | First element of tuple parameter | `CreateProofErr::Rejected` |
61+
| `host_sign_raw` | `SigningPayloadRaw.account` field | `SigningErr::PermissionDenied` |
62+
| `host_sign_payload` | `SigningPayload.account` field | `SigningErr::PermissionDenied` |
63+
| `host_create_transaction` | First element of tuple parameter | `CreateTransactionErr::PermissionDenied` |
64+
| `remote_statement_store_create_proof` | First element of tuple parameter | `StatementProofErr::Rejected` |
65+
| `host_payment_top_up` | `PaymentTopUpSource::ProductAccount` variant | `PaymentTopUpErr::Rejected` |
66+
67+
### API changes
68+
69+
No new methods or types are introduced. The existing error variants (`Rejected`, `PermissionDenied`) already cover the denial case. The change is purely behavioral: the host MUST perform the permission check before dispatching to the handler.
70+
71+
### Interaction with existing permission systems
72+
73+
- **Remote permissions (RFC-0002)**: Account-access permission is orthogonal to remote permissions. A product that has `RemotePermission::ChainSubmit` still needs account-access permission before calling `host_create_transaction` with a cross-domain `ProductAccountId`.
74+
- **Signing confirmation flow**: The per-operation signing confirmation (the dialog that shows "sign this payload?") remains unchanged. Account-access permission is checked first; if granted, the signing confirmation flow proceeds as before.
75+
- **`host_account_get_root` (RFC-0010)**: Root account access has its own independent JIT permission. This RFC does not affect it.
76+
77+
### Implementation guidance for `host-container`
78+
79+
In the `host-container` package, the affected slots should be changed from `makeNotImplementedSlot` to a new pattern (e.g. `makeAccountGatedRequestSlot`) that:
80+
81+
1. Extracts the `ProductAccountId` from the incoming request payload.
82+
2. Checks the permission cache.
83+
3. If not cached, delegates to a host-provided permission callback to prompt the user.
84+
4. On approval, caches the grant and calls the handler.
85+
5. On denial, returns the appropriate error without calling the handler.
86+
87+
## Drawbacks
88+
89+
**Prompt fatigue.** Products that legitimately need cross-domain account access will trigger a permission prompt on each new `ProductAccountId`. For products that access many accounts across different domains, this could be disruptive. The same-domain optimization mitigates the common case.
90+
91+
**Per-derivation-index granularity may be too fine.** Requiring separate grants for `("example.dot", 0)` and `("example.dot", 1)` provides maximum privacy but could annoy users when a product uses multiple derivation indices under the same domain. An alternative would be to grant per-`DotNsIdentifier` (see Alternatives).
92+
93+
**No revocation signal.** Like other permission-based methods (RFC-0002, RFC-0010), there is no push notification to the product if the user later revokes the grant. Products should handle rejection errors at any point.
94+
95+
## Alternatives
96+
97+
### Per-DotNsIdentifier grants (instead of per-ProductAccountId)
98+
99+
A simpler model would grant access to all derivation indices under a `DotNsIdentifier` with a single prompt. This reduces prompt frequency but allows a product to enumerate all derivation indices under a domain once access is granted. This may be acceptable if the privacy concern is primarily about cross-domain leakage rather than intra-domain enumeration.
100+
101+
### Domain-scoping enforcement (no prompts)
102+
103+
Instead of a JIT prompt, the host could silently reject any request where the `DotNsIdentifier` does not match the calling product's registered identity. This is simpler but prevents legitimate cross-domain use cases (e.g. a product that aggregates accounts across multiple DotNS domains with user consent).
104+
105+
### Combine with `host_account_get_root` permission
106+
107+
If a product has already received `host_account_get_root` approval (RFC-0010), the host could implicitly grant access to the root account's `ProductAccountId` without an additional prompt. This RFC does not mandate this optimization but hosts MAY implement it.
108+
109+
## Unresolved Questions
110+
111+
1. **Grant granularity.** Should grants be per-`ProductAccountId` (as proposed) or per-`DotNsIdentifier`? The former is more private; the latter is more ergonomic. Feedback from host implementors is needed.
112+
113+
2. **`host_payment_top_up` with `PrivateKey` source.** The `PaymentTopUpSource` enum also has a `PrivateKey` variant that does not use `ProductAccountId`. Should payment top-ups from private keys have their own permission gate? This is out of scope for this RFC but worth considering.
114+
115+
3. **Batch consent.** Should there be a mechanism for a product to declare all the `ProductAccountId`s it intends to use upfront, so the user gets a single prompt? This would parallel `remote_permission`'s batching model.
116+
117+
4. **Re-prompt policy.** If the user denied access in a previous session, should the product be able to trigger a new prompt in a new session? The protocol is silent on this; it is left to host implementations.

0 commit comments

Comments
 (0)