Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

Docs: recent sign-in on every page and endpoint it gates, 90-day session cap in limits (#89)

#86 documented Confirm it's you on the sign-in and security pages. This
adds it where people meet it: creating tokens and agents, deleting and
transferring a repository, deleting an organization, the six API
operations it gates (with the 403 and its confirm URL), the REST errors
table, and two rows in Limits (session cap and the 2-hour window).


Claude-Session: https://claude.ai/code/session_016q5wwBFck8JHq14gHUM2CL

Co-authored-by: Claude <noreply@anthropic.com>
Canberk Demirci authored · GitHub committed signed1 parent 8a8ac92commit b5b97a43723dad1a1856806eee0cd76f13f94915Browse files
7 files changed+14−10
Files (7)
modifiedapps/diffinery/docs/accounts/organizations.md+1−1View file
@@ -351,7 +351,7 @@
351351 2. In the **Danger zone**, under **Delete this organization**, type the organization's login (for example `acme`) in the confirmation box.
352352 3. Click **Delete this organization**.
353353
354-You see `Deleted acme.` and land on your home page.
354+You see `Deleted acme.` and land on your home page. If you signed in more than 2 hours ago, Diffinery first asks you to [Confirm it's you](/docs/security/overview#confirming-its-you) with your passkey.
355355
356356 ### Delete with the API
357357
modifiedapps/diffinery/docs/accounts/tokens.md+2−0View file
@@ -64,6 +64,8 @@
6464
6565 ## Create a token
6666
67+Creating a token needs a sign-in from the last 2 hours. If yours is older, Diffinery asks you to [Confirm it's you](/docs/security/overview#confirming-its-you) with your passkey first.
68+
6769 ### In the web UI
6870
6971 1. Go to **Settings → Access tokens** (`/settings/tokens`).
modifiedapps/diffinery/docs/agents/overview.md+1−1View file
@@ -15,7 +15,7 @@
1515
1616 ## Create an agent
1717
18-Only people can create agents. To create an agent owned by an organization, you must be an owner or admin of that organization.
18+Only people can create agents. To create an agent owned by an organization, you must be an owner or admin of that organization. Creating an agent or an agent token needs a sign-in from the last 2 hours; if yours is older, Diffinery asks you to [Confirm it's you](/docs/security/overview#confirming-its-you) with your passkey first.
1919
2020 The agent's login follows the same rules as usernames: letters, digits, `-` and `_`, starting with a letter or digit, up to 100 characters, no dots, and not a reserved name (such as `api`, `mcp`, `settings` or `admin`).
2121
modifiedapps/diffinery/docs/reference/limits.md+2−1View file
@@ -11,7 +11,8 @@
1111
1212 | Limit | Value | Applies to |
1313 | --- | --- | --- |
14-| Browser session lifetime | 30 days, extended to 30 days from now on each day the session is used | The session cookie you get after signing in with a passkey, a sign-in link or email recovery. An unused session ends 30 days after its last extension. |
14+| Browser session lifetime | 30 days, extended to 30 days from now on each day the session is used, never past 90 days after sign-in | The session cookie you get after signing in with a passkey, a sign-in link or email recovery. An unused session ends 30 days after its last extension, and every session ends 90 days after its sign-in. |
15+| Recent sign-in for sensitive actions | 2 hours | Adding or removing a passkey, creating a token, an agent or an agent token, changing your username, issuing a sign-in link, and deleting or transferring a repository or deleting an organization. An older browser sign-in gets **Confirm it's you** (`403` with a `confirm` URL over the API). See [Confirming it's you](/docs/security/overview#confirming-its-you). |
1516 | Passkey browser prompt | 2 minutes | Time to complete the browser's passkey dialog when signing in or adding a passkey. |
1617 | Passkey sign-in attempt | 5 minutes | A sign-in or registration that started more than 5 minutes ago can't be completed; start again. |
1718 | Sign-in link lifetime | 24 hours, single use | Links a site administrator sends with **Sign-in link** in **Settings → People and invitations**. |
modifiedapps/diffinery/docs/reference/rest-api.md+1−1View file
@@ -335,7 +335,7 @@
335335 | `205 Reset Content` | `PUT /api/v3/notifications`: every notification marked as read. | |
336336 | `400 Bad Request` | The body is not valid JSON, or the URL has a malformed percent-encoding. | `request body must be JSON`, `Problems parsing JSON`, `malformed percent-encoding in the URL` |
337337 | `401 Unauthorized` | No credentials where they are needed, or a bad token. | See [Authentication](#authentication). |
338-| `403 Forbidden` | You are signed in but your role, token scope or account kind doesn't allow this. | `write access required`, `admin access required`, `this token needs the write scope`, `this token is limited to repositories and can't change account settings`, `repository is archived (read-only)`, `agents cannot change branch protection`, `agents cannot manage secrets`, `agents cannot manage variables`, `agents can't dismiss reviews`, `only the app that created a check run can update it`, `cross-site request refused`, `Resource not accessible by integration (...)` (a workflow's `GITHUB_TOKEN`) |
338+| `403 Forbidden` | You are signed in but your role, token scope or account kind doesn't allow this. | `write access required`, `admin access required`, `this token needs the write scope`, `this token is limited to repositories and can't change account settings`, `repository is archived (read-only)`, `agents cannot change branch protection`, `agents cannot manage secrets`, `agents cannot manage variables`, `agents can't dismiss reviews`, `only the app that created a check run can update it`, `cross-site request refused`, `Resource not accessible by integration (...)` (a workflow's `GITHUB_TOKEN`), `Confirm it's you: sign in with your passkey again to do this.` (a sensitive call from a browser session signed in more than 2 hours ago; the body has a `confirm` URL) |
339339 | `404 Not Found` | The resource doesn't exist, or it is private and you can't see it. Diffinery doesn't reveal whether a private repository exists, and draft releases are hidden from readers the same way. | `repository not found`, `Branch not found`, `Not Found` |
340340 | `405 Method Not Allowed` | A pull request merge was refused by policy. | `pull request is not mergeable: ...` (followed by the blockers), `main requires a merge queue: add the pull request to the queue`, `main requires linear history; use squash or rebase` |
341341 | `409 Conflict` | Someone else changed the branch, ref or file first, or a merge conflicts. Fetch the latest state and retry. | `branch moved; refresh and retry`, `head branch was modified; review and try again`, `README.md does not match <sha>`, `Merge conflict`, `Git Repository is empty.`, `Already exists - Variable already exists` |
modifiedapps/diffinery/docs/repositories/overview.md+1−0View file
@@ -388,6 +388,7 @@
388388
389389 - **Web UI**: **Settings → General → Danger zone → Delete this repository**. Type the full name (for example `acme/website`) and click **Delete this repository**.
390390 - **API**: `DELETE /api/v3/repos/acme/website` returns `204 No Content`. It needs an admin role. Agents can't delete repositories (`agents cannot delete repositories`, HTTP 403).
391+- **Recent sign-in**: from a browser whose sign-in is more than 2 hours old, Diffinery first asks you to [Confirm it's you](/docs/security/overview#confirming-its-you) with your passkey. Transferring a repository asks the same.
391392
392393 Deletions are recorded in the audit log as `repo.delete`. The repository's owner (for an organization, its owners and admins) gets an email, unless they deleted it themselves.
393394
modifiedapps/diffinery/src/docs/api-meta.ts+6−6View file
@@ -137,7 +137,7 @@
137137 "DELETE /orgs/{org}": {
138138 summary: "Delete an organization",
139139 description:
140- "Owners only (or site admins). Refused with 422 while the organization still owns repositories (transfer or delete them first), container images, or an import in progress. Its members, pending invitations, secrets and variables are removed, its agents disabled and their tokens revoked, and its login freed. Repositories transferred out of it keep redirecting from their old URLs. Audited.",
140+ "Owners only (or site admins). Refused with 422 while the organization still owns repositories (transfer or delete them first), container images, or an import in progress. Its members, pending invitations, secrets and variables are removed, its agents disabled and their tokens revoked, and its login freed. Repositories transferred out of it keep redirecting from their old URLs. Audited. From a browser session whose sign-in is more than 2 hours old: 403 `Confirm it's you: sign in with your passkey again to do this.` with a `confirm` URL (see /docs/security/overview#confirming-its-you).",
141141 access: "org-admin",
142142 status: 202,
143143 },
@@ -230,11 +230,11 @@
230230 archived: ["boolean", "Archive (read-only) or unarchive."],
231231 },
232232 },
233- "DELETE /repos/{owner}/{repo}": { summary: "Delete a repository", description: "People only; agents can't delete repositories. Works on an archived repository too. Audited.", access: "admin", status: 204 },
233+ "DELETE /repos/{owner}/{repo}": { summary: "Delete a repository", description: "People only; agents can't delete repositories. Works on an archived repository too. Audited. From a browser session whose sign-in is more than 2 hours old: 403 `Confirm it's you: sign in with your passkey again to do this.` with a `confirm` URL (see /docs/security/overview#confirming-its-you).", access: "admin", status: 204 },
234234 "POST /repos/{owner}/{repo}/transfer": {
235235 summary: "Transfer a repository",
236236 description:
237- "Moves the repository to `new_owner`: the caller's own account, or an organization where the caller is an owner or admin. Needs admin access to the repository (a personal repository: its owner) and a token with the admin scope; agents, job tokens (`GITHUB_TOKEN`) and sandbox tokens are refused. 422 when the new owner already has a repository (or a container image of a moved image's name) with that name. Git data, issues, pull requests, Actions runs, secrets, variables, environments, webhooks and caches move with it, and so do container images linked to it. Direct collaborators keep their access; grants that came from the old organization (its members, their agents, its agents) are dropped, and the new owner's organization secrets and variables apply. The old owner/name keeps working for anyone who can read the repository, as on GitHub: API calls get a 301 (reads) or 307 (anything else) to the new URL, web pages a 301, Git (with a `This repository moved` notice on push) and MCP are served under the old name, and image names resolve to the moved images. Creating a repository at the old name ends the redirect. Admins of both owners get an email. Audited as `repo.transfer`. Returns the repository.",
237+ "Moves the repository to `new_owner`: the caller's own account, or an organization where the caller is an owner or admin. Needs admin access to the repository (a personal repository: its owner) and a token with the admin scope; agents, job tokens (`GITHUB_TOKEN`) and sandbox tokens are refused. 422 when the new owner already has a repository (or a container image of a moved image's name) with that name. Git data, issues, pull requests, Actions runs, secrets, variables, environments, webhooks and caches move with it, and so do container images linked to it. Direct collaborators keep their access; grants that came from the old organization (its members, their agents, its agents) are dropped, and the new owner's organization secrets and variables apply. The old owner/name keeps working for anyone who can read the repository, as on GitHub: API calls get a 301 (reads) or 307 (anything else) to the new URL, web pages a 301, Git (with a `This repository moved` notice on push) and MCP are served under the old name, and image names resolve to the moved images. Creating a repository at the old name ends the redirect. Admins of both owners get an email. Audited as `repo.transfer`. Returns the repository. From a browser session whose sign-in is more than 2 hours old: 403 `Confirm it's you: sign in with your passkey again to do this.` with a `confirm` URL (see /docs/security/overview#confirming-its-you).",
238238 access: "admin",
239239 status: 202,
240240 body: { new_owner: ["string", "Login of the user or organization to transfer to.", true], new_name: ["string", "A new name for the repository (default: keep its name)."] },
@@ -1302,7 +1302,7 @@
13021302 "GET /diffinery/tokens": { summary: "List your tokens", access: "account" },
13031303 "POST /diffinery/tokens": {
13041304 summary: "Create a token",
1305- description: "From a browser session only: no token can create a token. The token value is returned once. Unknown scopes or an empty list fail with 422, and so does a repository you can't see (`unknown repository owner/name`).",
1305+ description: "From a browser session only: no token can create a token. The token value is returned once. Unknown scopes or an empty list fail with 422, and so does a repository you can't see (`unknown repository owner/name`). From a browser session whose sign-in is more than 2 hours old: 403 `Confirm it's you: sign in with your passkey again to do this.` with a `confirm` URL (see /docs/security/overview#confirming-its-you).",
13061306 access: "account",
13071307 status: 201,
13081308 body: {
@@ -1321,7 +1321,7 @@
13211321 "GET /diffinery/agents": { summary: "List your agents", description: "Agents you own, and agents of organizations you administer.", access: "account" },
13221322 "POST /diffinery/agents": {
13231323 summary: "Create an agent",
1324- description: "People, from a browser session only (no token can create a token). Returns the agent and its first token (write scope, 90 days, unless you say otherwise). Unknown scopes or an empty list fail with 422.",
1324+ description: "People, from a browser session only (no token can create a token). Returns the agent and its first token (write scope, 90 days, unless you say otherwise). Unknown scopes or an empty list fail with 422. From a browser session whose sign-in is more than 2 hours old: 403 `Confirm it's you: sign in with your passkey again to do this.` with a `confirm` URL (see /docs/security/overview#confirming-its-you).",
13251325 access: "account",
13261326 status: 201,
13271327 body: {
@@ -1334,7 +1334,7 @@
13341334 },
13351335 "POST /diffinery/agents/{login}/tokens": {
13361336 summary: "Create a token for an agent",
1337- description: "From a browser session only (no token can create a token): the agent's owner, or an owner or admin of the organization that owns it. Default scope `write`; unknown scopes or an empty list fail with 422. Audited as `agent.token.create`.",
1337+ description: "From a browser session only (no token can create a token): the agent's owner, or an owner or admin of the organization that owns it. Default scope `write`; unknown scopes or an empty list fail with 422. Audited as `agent.token.create`. From a browser session whose sign-in is more than 2 hours old: 403 `Confirm it's you: sign in with your passkey again to do this.` with a `confirm` URL (see /docs/security/overview#confirming-its-you).",
13381338 access: "account",
13391339 status: 201,
13401340 body: { name: ["string", "Token name."], scopes: ["string[]", "`read`, `write` (default) and/or `admin`."], expires_in_days: ["integer", "Lifetime in days."] },