Gives a self-hosted Provenance instance a real owner/operator, which it previously lacked — ownership was only per-tree, so no account could administer the instance.
Model
OWNER_EMAIL (comma-separated) names the instance owner(s). Derived at request time — no DB column, no migration, cannot drift from the env, survives DB resets.
Ownership requires a verified email (independent of REQUIRE_EMAIL_VERIFICATION). Registration is open, so without this someone could seize the role by registering the owner address first; verification ties it to inbox control.
GET /api/v1/admin/instance (owner-only): version, env, user/tree counts, configured AI providers. No tree data or PII — instance ownership is an operator role, not a privacy-engine bypass (documented invariant).
/users/me reports is_instance_owner; frontend adds an owner-only /admin page + conditional sidebar link (server-enforced via the InstanceOwner dependency, not just client-hidden).
Security review (ran before merge)
A 3-lens adversarial review (privacy-bypass / authz-edgecases / surface) with per-finding verification surfaced 8 findings; 2 confirmed and fixed in this PR:
Owner land-grab (medium) — is_instance_owner matched on email without checking verification → fixed by requiring email_verified_at, plus a regression test.
Admin page null-deref (low) — crashed on 401/5xx instead of failing closed → now renders a graceful owner-only/unavailable state.
The other 6 were rejected as false alarms (e.g. the /admin/instance aggregates were confirmed PII-free).
Tests
tests/test_instance_owner.py: owner matching (case-insensitive), the verified-email land-grab guard, /users/me, owner-only /admin. Suite 96 passing.
No migration. Activating it on a deployment is just OWNER_EMAIL=<you> in .env + a restart (and a verified account).
Gives a self-hosted Provenance instance a real **owner/operator**, which it previously lacked — ownership was only per-tree, so no account could administer the instance.
### Model
- `OWNER_EMAIL` (comma-separated) names the instance owner(s). **Derived at request time** — no DB column, no migration, cannot drift from the env, survives DB resets.
- Ownership requires a **verified email** (independent of `REQUIRE_EMAIL_VERIFICATION`). Registration is open, so without this someone could seize the role by registering the owner address first; verification ties it to inbox control.
- `GET /api/v1/admin/instance` (owner-only): version, env, user/tree counts, configured AI providers. **No tree data or PII** — instance ownership is an operator role, *not* a privacy-engine bypass (documented invariant).
- `/users/me` reports `is_instance_owner`; frontend adds an owner-only `/admin` page + conditional sidebar link (**server-enforced** via the `InstanceOwner` dependency, not just client-hidden).
### Security review (ran before merge)
A 3-lens adversarial review (privacy-bypass / authz-edgecases / surface) with per-finding verification surfaced 8 findings; 2 confirmed and **fixed in this PR**:
1. **Owner land-grab** (medium) — `is_instance_owner` matched on email without checking verification → fixed by requiring `email_verified_at`, plus a regression test.
2. **Admin page null-deref** (low) — crashed on 401/5xx instead of failing closed → now renders a graceful owner-only/unavailable state.
The other 6 were rejected as false alarms (e.g. the `/admin/instance` aggregates were confirmed PII-free).
### Tests
`tests/test_instance_owner.py`: owner matching (case-insensitive), the verified-email land-grab guard, `/users/me`, owner-only `/admin`. **Suite 96 passing.**
No migration. Activating it on a deployment is just `OWNER_EMAIL=<you>` in `.env` + a restart (and a verified account).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Provenance had no system-level owner: ownership was only per-tree
(TreeMembership), so a self-hosted instance had no operator account and no
instance-admin surface. This adds one, declared by environment per the project's
twelve-factor rule.
- OWNER_EMAIL (comma-separated): the account(s) named here are instance owners.
Derived at request time — no DB column, no migration, can't drift from the env,
survives DB resets. is_instance_owner()/InstanceOwner dependency in api/deps.py.
- Ownership requires a VERIFIED email (independent of REQUIRE_EMAIL_VERIFICATION).
Registration is open, so without this an attacker could seize the role by
registering the owner address first; verification ties it to inbox control.
- GET /api/v1/admin/instance (owner-only): operational status — version, env,
user/tree counts, configured AI providers. Deliberately exposes no tree data
or PII: instance ownership is an operator role, NOT a privacy-engine bypass.
- /users/me reports is_instance_owner; frontend gains an owner-only /admin page
and a conditional sidebar link (server-enforced, not just client-hidden).
Found-and-fixed by an adversarial security review before merge: the
verified-email land-grab (above) and a frontend null-deref where the admin page
crashed on 401/5xx instead of failing closed.
Docs: .env.example + ARCHITECTURE (notes the not-a-privacy-bypass boundary and
the verified-email requirement). Tests: owner matching, the land-grab guard,
/users/me, and owner-only /admin. Suite 96 passing.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Justin Paul <[email protected]>
justin
merged commit 15504ba6e1 into main2026-06-09 23:17:09 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Gives a self-hosted Provenance instance a real owner/operator, which it previously lacked — ownership was only per-tree, so no account could administer the instance.
Model
OWNER_EMAIL(comma-separated) names the instance owner(s). Derived at request time — no DB column, no migration, cannot drift from the env, survives DB resets.REQUIRE_EMAIL_VERIFICATION). Registration is open, so without this someone could seize the role by registering the owner address first; verification ties it to inbox control.GET /api/v1/admin/instance(owner-only): version, env, user/tree counts, configured AI providers. No tree data or PII — instance ownership is an operator role, not a privacy-engine bypass (documented invariant)./users/mereportsis_instance_owner; frontend adds an owner-only/adminpage + conditional sidebar link (server-enforced via theInstanceOwnerdependency, not just client-hidden).Security review (ran before merge)
A 3-lens adversarial review (privacy-bypass / authz-edgecases / surface) with per-finding verification surfaced 8 findings; 2 confirmed and fixed in this PR:
is_instance_ownermatched on email without checking verification → fixed by requiringemail_verified_at, plus a regression test.The other 6 were rejected as false alarms (e.g. the
/admin/instanceaggregates were confirmed PII-free).Tests
tests/test_instance_owner.py: owner matching (case-insensitive), the verified-email land-grab guard,/users/me, owner-only/admin. Suite 96 passing.No migration. Activating it on a deployment is just
OWNER_EMAIL=<you>in.env+ a restart (and a verified account).🤖 Generated with Claude Code