From f416522bca81bb7f765e906aefd8bb4f38c25679 Mon Sep 17 00:00:00 2001 From: "@permadeath.com" Date: Fri, 11 Sep 2026 17:49:57 -0400 Subject: [PATCH] fix(policy-site): refuse only-one-client's other apps at the write too A Cedar write now carries its client, so the policy refuses writes made with a token another app already holds. A write with the account's own credential has no client and stays allowed. Co-Authored-By: Claude Opus 5 (1M context) Change-Id: Iee4caf9b646bb48ba7ece897121ef33e447ae59a --- .../recommended/only-one-client.cases.json | 34 +++++++++++++++++-- policy-site/recommended/only-one-client.json | 6 ++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/policy-site/recommended/only-one-client.cases.json b/policy-site/recommended/only-one-client.cases.json index 0921e5e9..c30152ad 100644 --- a/policy-site/recommended/only-one-client.cases.json +++ b/policy-site/recommended/only-one-client.cases.json @@ -47,8 +47,8 @@ } }, { - "name": "a write through another app, which it does not judge", - "expect": "allow", + "name": "a write through another app, with a token it already holds", + "expect": "deny", "write": { "account": { "did": "did:web:agent.pds.example", "handle": "agent.pds.example" }, "clientId": "https://other-app.example/oauth-client-metadata.json", @@ -60,5 +60,35 @@ "text": "hello" } } + }, + { + "name": "a write through the harness app", + "expect": "allow", + "write": { + "account": { "did": "did:web:agent.pds.example", "handle": "agent.pds.example" }, + "clientId": "https://harness.example/oauth-client-metadata.json", + "collection": "app.bsky.feed.post", + "rkey": "3lbwaaaaaaa2a", + "after": { + "$type": "app.bsky.feed.post", + "createdAt": "2026-09-11T11:59:00.000Z", + "text": "hello" + } + } + }, + { + "name": "a write with the account's own credential, through no app", + "expect": "allow", + "write": { + "account": { "did": "did:web:agent.pds.example", "handle": "agent.pds.example" }, + "clientId": null, + "collection": "app.bsky.feed.post", + "rkey": "3lbwaaaaaaa2a", + "after": { + "$type": "app.bsky.feed.post", + "createdAt": "2026-09-11T11:59:00.000Z", + "text": "hello" + } + } } ] diff --git a/policy-site/recommended/only-one-client.json b/policy-site/recommended/only-one-client.json index b368b3de..0738892c 100644 --- a/policy-site/recommended/only-one-client.json +++ b/policy-site/recommended/only-one-client.json @@ -1,10 +1,10 @@ { "$type": "bot.did.policy", "name": "only-one-client", - "description": "Refuses sign-in and token issue to every app except the one whose client metadata is served from harness.example. It judges no writes, so a token issued before it applied keeps working until it expires.", - "actions": ["oauth.authorize", "oauth.token"], + "description": "Refuses every app except the one whose client metadata is served from harness.example: at sign-in, at token issue, and on every write made with a token it already holds. A write the account makes with its own credential comes through no app, and is allowed.", + "actions": ["oauth.authorize", "oauth.token", "record.write"], "document": { "$type": "bot.did.policy#cedar", - "source": "@id(\"only-harness\")\n@reason(\"only the operator's harness app may act for this account\")\nforbid(\n principal,\n action in [Didbot::Action::\"oauth.authorize\", Didbot::Action::\"oauth.token\"],\n resource\n)\nunless { resource.id like \"https://harness.example/*\" };\n" + "source": "@id(\"only-harness\")\n@reason(\"only the operator's harness app may act for this account\")\nforbid(\n principal,\n action in [Didbot::Action::\"oauth.authorize\", Didbot::Action::\"oauth.token\"],\n resource\n)\nunless { resource.id like \"https://harness.example/*\" };\n\n@id(\"only-harness-writes\")\n@reason(\"only the operator's harness app may act for this account\")\nforbid(principal, action == Didbot::Action::\"record.write\", resource)\nwhen {\n context has client &&\n !(context.client.id like \"https://harness.example/*\")\n};\n" } } -- 2.51.2