S38: docs — user guides (quickstart/ssh/https/PATs/account/2FA/issues/PRs/branch-protection/notifs/webhooks/search/orgs/markdown)
- SHA
2f1f48e966085953e9e2541301a393b66637fc05- Parents
-
88222cc - Tree
892e33b
2f1f48e
2f1f48e966085953e9e2541301a393b66637fc0588222cc
892e33b| Status | File | + | - |
|---|---|---|---|
| A |
docs/public/user/2fa.md
|
67 | 0 |
| A |
docs/public/user/account.md
|
75 | 0 |
| A |
docs/public/user/branch-protection.md
|
65 | 0 |
| A |
docs/public/user/https.md
|
70 | 0 |
| A |
docs/public/user/issues.md
|
67 | 0 |
| A |
docs/public/user/markdown.md
|
112 | 0 |
| A |
docs/public/user/notifications.md
|
70 | 0 |
| A |
docs/public/user/orgs.md
|
72 | 0 |
| A |
docs/public/user/personal-access-tokens.md
|
71 | 0 |
| A |
docs/public/user/pull-requests.md
|
78 | 0 |
| A |
docs/public/user/quickstart.md
|
69 | 0 |
| A |
docs/public/user/search.md
|
59 | 0 |
| A |
docs/public/user/ssh.md
|
67 | 0 |
| A |
docs/public/user/webhooks.md
|
118 | 0 |
docs/public/user/2fa.mdadded@@ -0,0 +1,67 @@ | ||
| 1 | +# Two-factor authentication | |
| 2 | + | |
| 3 | +Two-factor authentication (2FA) requires a second proof — beyond | |
| 4 | +your password — before you can sign in. shithub supports | |
| 5 | +**TOTP** (Time-based One-Time Password): six-digit codes from an | |
| 6 | +authenticator app that change every 30 seconds. | |
| 7 | + | |
| 8 | +Strongly recommended. Account takeover is the most common bad | |
| 9 | +outcome on a forge; a stolen password by itself can't sign in if | |
| 10 | +2FA is on. | |
| 11 | + | |
| 12 | +## Setting up TOTP | |
| 13 | + | |
| 14 | +1. Settings → Account security → Two-factor authentication → | |
| 15 | + "Enable TOTP". | |
| 16 | +2. Open your authenticator (Google Authenticator, 1Password, | |
| 17 | + Authy, etc.) and scan the QR code. The text-form secret is | |
| 18 | + shown beneath the QR if your app needs it. | |
| 19 | +3. Enter the six-digit code from the app to confirm enrollment. | |
| 20 | +4. **Save the recovery codes** that appear. There are 10. Each is | |
| 21 | + single-use. Store them somewhere your authenticator-device | |
| 22 | + loss won't take with it (password manager, paper safe). | |
| 23 | + | |
| 24 | +You're now enrolled. The next sign-in will ask for the code after | |
| 25 | +the password. | |
| 26 | + | |
| 27 | +## Recovery codes | |
| 28 | + | |
| 29 | +If you lose your authenticator (phone died, factory reset, etc.), | |
| 30 | +recovery codes are your way back in. | |
| 31 | + | |
| 32 | +- Each code works **once**. | |
| 33 | +- Used codes are crossed off — you can see which are spent. | |
| 34 | +- When you have ≤2 unused codes left, the UI nudges you to | |
| 35 | + regenerate. Regenerating invalidates all previous codes. | |
| 36 | + | |
| 37 | +If you exhaust all 10 codes, the only path back in is operator | |
| 38 | +intervention — they cannot give you your codes back, but they can | |
| 39 | +disable 2FA on the account after verifying your identity through | |
| 40 | +a side channel. | |
| 41 | + | |
| 42 | +## Disabling 2FA | |
| 43 | + | |
| 44 | +Settings → Account security → "Disable TOTP". Requires entering | |
| 45 | +the current TOTP code. | |
| 46 | + | |
| 47 | +This is recorded in your audit log. If you didn't disable 2FA, | |
| 48 | +treat that audit row as evidence of compromise and rotate | |
| 49 | +everything (password, all PATs, all SSH keys). | |
| 50 | + | |
| 51 | +## Sign-in flow with 2FA | |
| 52 | + | |
| 53 | +1. Username + password. | |
| 54 | +2. If correct, you're prompted for the six-digit code (or "use a | |
| 55 | + recovery code"). | |
| 56 | +3. Code accepted → signed in. | |
| 57 | + | |
| 58 | +## What 2FA does and doesn't protect | |
| 59 | + | |
| 60 | +- **Protects against** stolen passwords (phishing, leaked-DB | |
| 61 | + reuse, shoulder-surfing). | |
| 62 | +- **Does not protect against** stolen sessions — once you're | |
| 63 | + signed in, the session cookie is the access. Use "Sign out | |
| 64 | + everywhere" if a device is lost. | |
| 65 | +- **Does not protect** PAT-based git or API access — PATs are | |
| 66 | + separate credentials; rotate them on the same cadence as | |
| 67 | + passwords. | |
docs/public/user/account.mdadded@@ -0,0 +1,75 @@ | ||
| 1 | +# Account settings | |
| 2 | + | |
| 3 | +Your settings are split into a small number of pages reachable | |
| 4 | +from the avatar menu. | |
| 5 | + | |
| 6 | +## Profile | |
| 7 | + | |
| 8 | +Display name, bio, location, website, company, pronouns. Visible | |
| 9 | +on your profile page (`/<username>`). Markdown is **not** rendered | |
| 10 | +in the bio — it's plain text with hyperlinks auto-linked. | |
| 11 | + | |
| 12 | +## Emails | |
| 13 | + | |
| 14 | +You can have multiple email addresses on one account. One is | |
| 15 | +**primary** (used for notifications and as the default committer | |
| 16 | +match), the others are secondary (still match commits authored | |
| 17 | +with that email). | |
| 18 | + | |
| 19 | +Each email is independently verified — until you click the link | |
| 20 | +sent to it, that address can't be used as primary. | |
| 21 | + | |
| 22 | +## Password | |
| 23 | + | |
| 24 | +Change with the current password. Successful change invalidates | |
| 25 | +**every** session except the current one (the session-epoch | |
| 26 | +mechanism). Other devices will be signed out. | |
| 27 | + | |
| 28 | +## Two-factor authentication | |
| 29 | + | |
| 30 | +See [TOTP & recovery codes](./2fa.md). Strongly recommended. | |
| 31 | + | |
| 32 | +## Sessions | |
| 33 | + | |
| 34 | +Lists every active session with device + last-used time. "Sign | |
| 35 | +out everywhere" bumps your session epoch — every other session is | |
| 36 | +killed instantly. Use this if you suspect an unauthorized sign-in. | |
| 37 | + | |
| 38 | +## SSH and GPG keys | |
| 39 | + | |
| 40 | +- **SSH keys** authenticate `git@shithub.example:...` operations. | |
| 41 | +- **GPG keys** verify signed commits — when a commit's signature | |
| 42 | + matches a registered GPG key, the commit shows a "Verified" | |
| 43 | + badge in history. | |
| 44 | + | |
| 45 | +Each key shows last-used timestamp; rotate when devices are lost. | |
| 46 | + | |
| 47 | +## Personal access tokens | |
| 48 | + | |
| 49 | +See [PATs](./personal-access-tokens.md). | |
| 50 | + | |
| 51 | +## Notifications | |
| 52 | + | |
| 53 | +See [Notifications](./notifications.md). | |
| 54 | + | |
| 55 | +## Audit log | |
| 56 | + | |
| 57 | +Settings → Audit log lists security-relevant actions on your | |
| 58 | +account: sign-ins, password changes, 2FA toggles, key add/remove, | |
| 59 | +PAT create/revoke, suspension events. Each row carries the IP and | |
| 60 | +user-agent at the time. Export as JSON if you need to share with | |
| 61 | +an operator during incident response. | |
| 62 | + | |
| 63 | +## Delete account | |
| 64 | + | |
| 65 | +The bottom of Settings → Account has a delete button. It requires | |
| 66 | +typing your username for confirmation. Deletion: | |
| 67 | + | |
| 68 | +- Marks the account for soft-delete with a 30-day grace. | |
| 69 | +- Hides your repos, issues, comments, and PRs immediately. | |
| 70 | +- Frees the username after grace; until then, the username is | |
| 71 | + reserved. | |
| 72 | + | |
| 73 | +Within the grace period, signing in restores the account fully. | |
| 74 | +After grace, the deletion is final; comments and issues you | |
| 75 | +authored are reattributed to a `ghost` user. | |
docs/public/user/branch-protection.mdadded@@ -0,0 +1,65 @@ | ||
| 1 | +# Branch protection & reviews | |
| 2 | + | |
| 3 | +Branch protection lets a repo admin guard specific branches — | |
| 4 | +typically `main` — from direct pushes, force-pushes, deletion, | |
| 5 | +and merges that don't meet the team's review/CI bar. | |
| 6 | + | |
| 7 | +Configured at Repository → Settings → Branches. | |
| 8 | + | |
| 9 | +## Per-rule controls | |
| 10 | + | |
| 11 | +A rule applies to one or more branches by name pattern (`main`, | |
| 12 | +`release/*`, etc.). Each rule can independently: | |
| 13 | + | |
| 14 | +- **Require pull request before merging** — direct pushes to the | |
| 15 | + branch are rejected. The only way in is a merged PR. | |
| 16 | +- **Require N approvals** — a PR can't merge without that many | |
| 17 | + approvals from users with write access. | |
| 18 | +- **Dismiss stale approvals on new commits** — when the PR head | |
| 19 | + moves, prior approvals are wiped. | |
| 20 | +- **Require approval from someone other than the last pusher** — | |
| 21 | + the author of the most recent push to the PR can't be one of | |
| 22 | + the approvers. | |
| 23 | +- **Require conversation resolution** — every line comment must | |
| 24 | + be marked "Resolved" before merge. | |
| 25 | +- **Require status checks** — list of CI check names. The PR's | |
| 26 | + head commit must report success on each before merge enables. | |
| 27 | +- **Require status checks to be up-to-date** — head must include | |
| 28 | + the latest base commit before checks count. | |
| 29 | +- **Restrict who can push** (for non-PR pushes if PRs aren't | |
| 30 | + required) — list of users/teams. | |
| 31 | +- **Disallow force-pushes**. | |
| 32 | +- **Disallow deletions**. | |
| 33 | +- **Lock branch** — read-only; no merges either. Used for | |
| 34 | + archived release lines. | |
| 35 | + | |
| 36 | +## How reviews count | |
| 37 | + | |
| 38 | +- Approvals from users with **read-only** access on the repo | |
| 39 | + don't count. | |
| 40 | +- An approval is dismissed if the reviewer is removed from the | |
| 41 | + repo. | |
| 42 | +- "Request changes" blocks merge until the same reviewer | |
| 43 | + re-reviews (Approve or Comment) — a different reviewer's | |
| 44 | + Approve does not lift the block. | |
| 45 | + | |
| 46 | +## Status checks | |
| 47 | + | |
| 48 | +Status checks come from external CI runners (or, in the future, | |
| 49 | +shithub's own runner). A check has: | |
| 50 | + | |
| 51 | +- **Context** — the name (e.g., `ci/lint`, `ci/test`). | |
| 52 | +- **State** — `pending`, `success`, `failure`, `error`. | |
| 53 | +- **Description** — short human text. | |
| 54 | +- **Target URL** — link to the run details. | |
| 55 | + | |
| 56 | +The first time a context name appears on the repo, you can add | |
| 57 | +it to the required-checks list. The PR's merge gate evaluates | |
| 58 | +the **head commit's most recent state per context**. | |
| 59 | + | |
| 60 | +## Bypass | |
| 61 | + | |
| 62 | +A repo admin can bypass branch protection for a single push | |
| 63 | +(per-action; not a permanent setting) — useful for emergencies. | |
| 64 | +The bypass is recorded in the audit log with the admin's id and | |
| 65 | +the affected commits. | |
docs/public/user/https.mdadded@@ -0,0 +1,70 @@ | ||
| 1 | +# Cloning over HTTPS with a PAT | |
| 2 | + | |
| 3 | +For git over HTTPS, you authenticate with a **personal access | |
| 4 | +token** (PAT), not your account password. This matches GitHub's | |
| 5 | +behavior since 2021 and reflects the same security thinking: | |
| 6 | +account passwords are more sensitive than scoped, revocable | |
| 7 | +tokens. | |
| 8 | + | |
| 9 | +## 1. Create a PAT | |
| 10 | + | |
| 11 | +Settings → Developer settings → Personal access tokens → "New | |
| 12 | +token". | |
| 13 | + | |
| 14 | +- **Note** — name the token after where you'll use it ("laptop", | |
| 15 | + "ci-runner-1"). Future-you will thank present-you. | |
| 16 | +- **Expiration** — pick the shortest interval that's tolerable. | |
| 17 | + Tokens you forget about are tokens an attacker eventually finds. | |
| 18 | +- **Scopes** — for git push/pull from a workstation, pick `repo` | |
| 19 | + (read+write). For read-only mirroring, `repo:read` is enough. | |
| 20 | + | |
| 21 | +When you submit, the token is shown **once**. Copy it immediately | |
| 22 | +into your password manager — we never display it again. | |
| 23 | + | |
| 24 | +## 2. Clone | |
| 25 | + | |
| 26 | +```sh | |
| 27 | +git clone https://shithub.example/<owner>/<repo>.git | |
| 28 | +``` | |
| 29 | + | |
| 30 | +When git asks for credentials: | |
| 31 | + | |
| 32 | +- **Username:** your shithub username. | |
| 33 | +- **Password:** the PAT. | |
| 34 | + | |
| 35 | +## 3. Cache credentials | |
| 36 | + | |
| 37 | +Typing the PAT every push gets old. Use a credential helper: | |
| 38 | + | |
| 39 | +- **macOS:** `git config --global credential.helper osxkeychain` | |
| 40 | +- **Windows:** `git config --global credential.helper manager` | |
| 41 | +- **Linux (GNOME):** `git config --global credential.helper | |
| 42 | + /usr/share/doc/git/contrib/credential/libsecret/...` | |
| 43 | +- **Anywhere, in a pinch:** `git config --global credential.helper | |
| 44 | + cache` (in-memory, default 15-minute TTL). | |
| 45 | + | |
| 46 | +The helper stores `(url, username, password)`; the next push to | |
| 47 | +the same host reuses it. | |
| 48 | + | |
| 49 | +## 4. Use a CI runner | |
| 50 | + | |
| 51 | +In CI, set the username/password as secrets and inject them via | |
| 52 | +the URL or `~/.netrc`. Use a token with the narrowest scope the | |
| 53 | +job needs and a short expiration. | |
| 54 | + | |
| 55 | +```sh | |
| 56 | +git clone https://x-access-token:${SHITHUB_PAT}@shithub.example/owner/repo.git | |
| 57 | +``` | |
| 58 | + | |
| 59 | +Because the token is in the URL, make sure your CI doesn't echo | |
| 60 | +the URL into logs. | |
| 61 | + | |
| 62 | +## When pushes fail | |
| 63 | + | |
| 64 | +| Symptom | Likely cause | | |
| 65 | +|------------------------------------------------------|-------------------------------------------| | |
| 66 | +| `403 Forbidden` on push | Token lacks `repo` write scope. | | |
| 67 | +| `401 Unauthorized` immediately | Wrong username, expired token, or the token was revoked. | | |
| 68 | +| `protected branch hook declined` | Branch protection requires PR + reviews — push to a feature branch instead. | | |
| 69 | +| `pre-receive hook declined: repo over quota` | Repo size cap hit; see your operator. | | |
| 70 | +| `error: failed to push some refs … updates were rejected` | Standard git non-fast-forward — pull/rebase first. | | |
docs/public/user/issues.mdadded@@ -0,0 +1,67 @@ | ||
| 1 | +# Issues | |
| 2 | + | |
| 3 | +Issues track bugs, ideas, and conversations against a repository. | |
| 4 | +Anyone with read access to a repo can see its issues; opening and | |
| 5 | +commenting depends on the repo's settings (open to all logged-in | |
| 6 | +users by default). | |
| 7 | + | |
| 8 | +## Opening an issue | |
| 9 | + | |
| 10 | +Repo → Issues → "New issue". Required: | |
| 11 | + | |
| 12 | +- **Title** — one line. | |
| 13 | +- **Body** — markdown ([reference](./markdown.md)). Drag-and-drop | |
| 14 | + attachments upload to the repo's blob store. | |
| 15 | + | |
| 16 | +Optional: | |
| 17 | + | |
| 18 | +- **Labels** — colored tags maintainers use for triage. | |
| 19 | +- **Assignees** — who's expected to handle it. | |
| 20 | +- **Milestone** — group issues toward a release/target. | |
| 21 | + | |
| 22 | +## References | |
| 23 | + | |
| 24 | +shithub auto-links these in issue + comment bodies: | |
| 25 | + | |
| 26 | +- `#123` — issue or PR in this repo. | |
| 27 | +- `owner/repo#123` — issue or PR in another repo. | |
| 28 | +- `@username` — user mention; they get a notification. | |
| 29 | +- `@org/team` — team mention; every member is notified. | |
| 30 | +- Commit SHAs (full or 7+ chars) — link to the commit page. | |
| 31 | + | |
| 32 | +## Closing | |
| 33 | + | |
| 34 | +Three ways an issue closes: | |
| 35 | + | |
| 36 | +- Manually, via the "Close" button. | |
| 37 | +- Via a referenced commit/PR — `Fixes #123` or `Closes #123` in a | |
| 38 | + merged PR's title or body auto-closes the issue. | |
| 39 | +- Via API. | |
| 40 | + | |
| 41 | +Closed issues stay visible; you can reopen them. | |
| 42 | + | |
| 43 | +## Comments + reactions | |
| 44 | + | |
| 45 | +Each comment supports the same markdown as the body. Reactions | |
| 46 | +(👍 👎 😄 🎉 😕 ❤️ 🚀 👀) live on every issue/comment as a way to | |
| 47 | +signal agreement without adding "+1" noise. | |
| 48 | + | |
| 49 | +## Locking | |
| 50 | + | |
| 51 | +Maintainers can lock a conversation. Locked issues accept no new | |
| 52 | +comments or reactions; existing content stays. | |
| 53 | + | |
| 54 | +## Filters and search | |
| 55 | + | |
| 56 | +The Issues list supports filters: | |
| 57 | + | |
| 58 | +- `is:open` / `is:closed` | |
| 59 | +- `author:<user>` | |
| 60 | +- `assignee:<user>` | |
| 61 | +- `label:"good first issue"` | |
| 62 | +- `milestone:"v1.0"` | |
| 63 | +- `mentions:<user>` | |
| 64 | +- `commenter:<user>` | |
| 65 | +- Sort: newest, oldest, most commented, recently updated. | |
| 66 | + | |
| 67 | +Free-text after the filter narrows by title + body. | |
docs/public/user/markdown.mdadded@@ -0,0 +1,112 @@ | ||
| 1 | +# Markdown reference | |
| 2 | + | |
| 3 | +shithub renders user-authored markdown — issue bodies, comments, | |
| 4 | +PR descriptions, READMEs — through a CommonMark + GFM parser | |
| 5 | +with a UGC-safe sanitizer. The set we support is close to | |
| 6 | +GitHub's, with a few deliberate omissions. | |
| 7 | + | |
| 8 | +## Basics | |
| 9 | + | |
| 10 | +``` | |
| 11 | +# Heading 1 | |
| 12 | +## Heading 2 | |
| 13 | +### Heading 3 | |
| 14 | + | |
| 15 | +**bold**, *italic*, ~~strikethrough~~, `inline code`. | |
| 16 | + | |
| 17 | +> A blockquote. | |
| 18 | + | |
| 19 | +- bullet | |
| 20 | +- list | |
| 21 | + | |
| 22 | +1. ordered | |
| 23 | +2. list | |
| 24 | + | |
| 25 | +[link text](https://example.com) | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | +--- (horizontal rule) | |
| 30 | +``` | |
| 31 | + | |
| 32 | +## Code | |
| 33 | + | |
| 34 | +Three-backtick fenced blocks with an optional language tag. The | |
| 35 | +language drives syntax highlighting (chroma). | |
| 36 | + | |
| 37 | +```` | |
| 38 | +```go | |
| 39 | +func main() { | |
| 40 | + fmt.Println("hello") | |
| 41 | +} | |
| 42 | +``` | |
| 43 | +```` | |
| 44 | + | |
| 45 | +## Tables | |
| 46 | + | |
| 47 | +GFM-style: | |
| 48 | + | |
| 49 | +``` | |
| 50 | +| Column A | Column B | | |
| 51 | +|----------|----------| | |
| 52 | +| cell 1 | cell 2 | | |
| 53 | +| cell 3 | cell 4 | | |
| 54 | +``` | |
| 55 | + | |
| 56 | +Alignment with `:`: | |
| 57 | + | |
| 58 | +``` | |
| 59 | +| Left | Center | Right | | |
| 60 | +|:-----|:------:|------:| | |
| 61 | +``` | |
| 62 | + | |
| 63 | +## Task lists | |
| 64 | + | |
| 65 | +``` | |
| 66 | +- [x] done | |
| 67 | +- [ ] todo | |
| 68 | +``` | |
| 69 | + | |
| 70 | +Checkboxes are clickable in issues and PRs you can edit. | |
| 71 | + | |
| 72 | +## References | |
| 73 | + | |
| 74 | +shithub auto-links these in any user-authored markdown: | |
| 75 | + | |
| 76 | +- `#123` — issue or PR in the current repo. | |
| 77 | +- `owner/repo#123` — issue/PR in another repo. | |
| 78 | +- `@username` — user mention; notifies them. | |
| 79 | +- `@org/team` — team mention. | |
| 80 | +- A SHA (full or 7+ chars) — commit link. | |
| 81 | + | |
| 82 | +## Emoji shortcodes | |
| 83 | + | |
| 84 | +`:rocket:` → 🚀, `:eyes:` → 👀, etc. The set matches GitHub's | |
| 85 | +shortcode list. | |
| 86 | + | |
| 87 | +## What we don't support | |
| 88 | + | |
| 89 | +- **Inline HTML** — sanitized away. Use markdown alternatives. | |
| 90 | +- **`<style>` / `<script>`** — sanitized away. | |
| 91 | +- **Custom HTML attributes** (`onclick`, `style`, `id` on arbitrary | |
| 92 | + elements) — stripped. | |
| 93 | +- **Footnotes** — not yet (planned). | |
| 94 | +- **MathJax / KaTeX** — not yet (planned). | |
| 95 | +- **Mermaid diagrams** — not yet (post-MVP). | |
| 96 | +- **Image attributes** like `<img width="200">` — strip-and-render | |
| 97 | + without the attribute. Use markdown image syntax with a small | |
| 98 | + thumbnail upstream. | |
| 99 | + | |
| 100 | +## Why a sanitizer? | |
| 101 | + | |
| 102 | +User-authored markdown is the largest XSS surface on a forge. | |
| 103 | +shithub renders through a single helper (`internal/markdown`) | |
| 104 | +that runs every input through a `bluemonday` UGC policy after | |
| 105 | +parsing. Anything outside the supported set is silently dropped, | |
| 106 | +not preserved as escaped text. | |
| 107 | + | |
| 108 | +## Previewing | |
| 109 | + | |
| 110 | +Issue and PR forms have a "Preview" tab that renders the markdown | |
| 111 | +through the exact same pipeline we use for the saved version. | |
| 112 | +What you see in preview is what you'll see after submit. | |
docs/public/user/notifications.mdadded@@ -0,0 +1,70 @@ | ||
| 1 | +# Notifications | |
| 2 | + | |
| 3 | +shithub notifies you when something happens that needs your | |
| 4 | +attention: an issue is assigned, a review is requested, a comment | |
| 5 | +mentions you, a watched repo has new activity. | |
| 6 | + | |
| 7 | +Two delivery channels: | |
| 8 | + | |
| 9 | +- **In-app inbox** at `/notifications`. | |
| 10 | +- **Email** to your primary email. | |
| 11 | + | |
| 12 | +Both are driven by the same routing rules. | |
| 13 | + | |
| 14 | +## Watch levels | |
| 15 | + | |
| 16 | +For each repo, your subscription level is one of: | |
| 17 | + | |
| 18 | +- **Ignore** — never notify, even on direct mentions. | |
| 19 | +- **Participating only** (default for repos you've never touched) | |
| 20 | + — notify only when something concerns you directly: you're | |
| 21 | + assigned, mentioned, your comment was replied to, your PR has | |
| 22 | + a review. | |
| 23 | +- **All activity** — every issue, PR, comment, push gets a | |
| 24 | + notification. | |
| 25 | +- **Custom** — pick which event categories you want. | |
| 26 | + | |
| 27 | +Set the level from the repo header's "Watch" dropdown. | |
| 28 | + | |
| 29 | +## Auto-subscribe | |
| 30 | + | |
| 31 | +You're auto-subscribed to: | |
| 32 | + | |
| 33 | +- Issues + PRs you opened. | |
| 34 | +- Issues + PRs you commented on (until you unsubscribe via the | |
| 35 | + "Unsubscribe" button on the thread). | |
| 36 | +- Issues + PRs you're assigned to or your review is requested on. | |
| 37 | + | |
| 38 | +Auto-subscribe lives at the **thread** level, not the repo level — | |
| 39 | +unsubscribing from one issue doesn't change anything else. | |
| 40 | + | |
| 41 | +## Reading the inbox | |
| 42 | + | |
| 43 | +The `/notifications` page lists unread + pinned threads. | |
| 44 | +- **Mark as read** — collapse and remove from the unread list. | |
| 45 | +- **Mark as done** — archive; comes back if there's new activity. | |
| 46 | +- **Mute thread** — never notify on this thread again, even on | |
| 47 | + new mentions. | |
| 48 | + | |
| 49 | +Filters: by repo, by reason (mention, review-requested, assigned, | |
| 50 | +etc.), and by read/unread. | |
| 51 | + | |
| 52 | +## Email behavior | |
| 53 | + | |
| 54 | +- Each notification is one email; we don't bundle (yet). | |
| 55 | +- Subject: `[<repo>] <issue title> (#<n>)`. | |
| 56 | +- Reply-by-email is **not supported** — replies bounce. We don't | |
| 57 | + want to be in the email-thread-state-management business. | |
| 58 | +- Each email has a one-click **HMAC-signed unsubscribe link** | |
| 59 | + that moves the thread to muted without a sign-in step. | |
| 60 | + | |
| 61 | +## Stop emails entirely | |
| 62 | + | |
| 63 | +Settings → Notifications → "Email delivery: never". The in-app | |
| 64 | +inbox keeps working. | |
| 65 | + | |
| 66 | +## How frequently we email | |
| 67 | + | |
| 68 | +There's no digest mode (yet). Emails go out as the events happen. | |
| 69 | +If that's too much, switch the repo to "Participating only" or | |
| 70 | +mute specific threads. | |
docs/public/user/orgs.mdadded@@ -0,0 +1,72 @@ | ||
| 1 | +# Organizations & teams | |
| 2 | + | |
| 3 | +Organizations group people and repos under a shared namespace. | |
| 4 | +A repo at `acme/widget` is owned by the `acme` org, and access | |
| 5 | +is managed through teams + direct collaborators. | |
| 6 | + | |
| 7 | +## Creating an org | |
| 8 | + | |
| 9 | +Account menu → "New organization". You'll need: | |
| 10 | + | |
| 11 | +- A name — same rules as usernames; cannot collide with an | |
| 12 | + existing user or org. | |
| 13 | +- A primary email (for billing/notifications; org settings only). | |
| 14 | + | |
| 15 | +The creator is the first owner. | |
| 16 | + | |
| 17 | +## Roles | |
| 18 | + | |
| 19 | +- **Owner** — full admin: settings, billing, member management, | |
| 20 | + any repo. There must always be at least one owner. | |
| 21 | +- **Member** — appears in the org's people list; sees public + the | |
| 22 | + private repos a team grants them. | |
| 23 | + | |
| 24 | +A user can be both an owner and a team member. | |
| 25 | + | |
| 26 | +## Inviting members | |
| 27 | + | |
| 28 | +Org → People → "Invite member". Type an existing username or | |
| 29 | +email. The invitee gets a notification + email; they accept on a | |
| 30 | +landing page and become a member immediately. | |
| 31 | + | |
| 32 | +Invitations expire after 7 days; resend or revoke from the same | |
| 33 | +page. | |
| 34 | + | |
| 35 | +## Removing members | |
| 36 | + | |
| 37 | +Owner → click a member → "Remove". The user loses team | |
| 38 | +memberships and any direct grants the org had given them. If they | |
| 39 | +authored issues/PRs, those stay; if they had pending invitations, | |
| 40 | +those are cancelled. | |
| 41 | + | |
| 42 | +## Teams | |
| 43 | + | |
| 44 | +Teams are how you grant repo access at scale. | |
| 45 | + | |
| 46 | +- **Create:** Org → Teams → "New team". | |
| 47 | +- **Members:** add org members; non-members must be invited to | |
| 48 | + the org first. | |
| 49 | +- **Repo access:** add repos with one of `read`, `triage`, `write`, | |
| 50 | + `maintain`, `admin`. Multiple teams can grant on the same repo; | |
| 51 | + the user's effective permission is the **maximum**. | |
| 52 | +- **One-level nesting:** a team can have a parent team. Members | |
| 53 | + of a parent team are also considered members of every child | |
| 54 | + team for permission purposes. | |
| 55 | + | |
| 56 | +## Mentioning a team | |
| 57 | + | |
| 58 | +`@acme/security` in any markdown body notifies every member of | |
| 59 | +that team. Useful for review requests and triage. | |
| 60 | + | |
| 61 | +## Org-owned repos | |
| 62 | + | |
| 63 | +Indistinguishable from user-owned repos in most ways; the | |
| 64 | +difference is that access is governed by team grants instead of | |
| 65 | +the owner being a single user. | |
| 66 | + | |
| 67 | +## Audit | |
| 68 | + | |
| 69 | +Org → Settings → Audit log surfaces org-level events: member | |
| 70 | +add/remove, team create/delete, repo transferred in/out, role | |
| 71 | +changes. Same per-row IP + user-agent capture as the personal | |
| 72 | +audit log. | |
docs/public/user/personal-access-tokens.mdadded@@ -0,0 +1,71 @@ | ||
| 1 | +# Personal access tokens | |
| 2 | + | |
| 3 | +Personal access tokens (PATs) are scoped, expirable credentials | |
| 4 | +you create from your account settings. They're how you | |
| 5 | +authenticate to the API and to git over HTTPS. | |
| 6 | + | |
| 7 | +PATs are **not** passwords: | |
| 8 | + | |
| 9 | +- They have an expiration. | |
| 10 | +- They have scopes — a token cannot do what its scopes don't grant. | |
| 11 | +- They are listed in your settings with a "last used" timestamp. | |
| 12 | +- They can be revoked individually without changing your password. | |
| 13 | + | |
| 14 | +## Scopes | |
| 15 | + | |
| 16 | +Pick the smallest set the consumer needs. | |
| 17 | + | |
| 18 | +| Scope | What it allows | | |
| 19 | +|----------------|----------------------------------------------------------------------| | |
| 20 | +| `repo:read` | Read repos you can already see (public + your private + collabs). | | |
| 21 | +| `repo` | Above + push, manage settings on repos you own/admin. | | |
| 22 | +| `user:read` | Read your profile + email. | | |
| 23 | +| `user` | Above + edit profile, emails. | | |
| 24 | +| `notifications`| Read + mark-read your notification inbox. | | |
| 25 | +| `webhooks` | Manage webhooks on repos you own/admin. | | |
| 26 | +| `admin:org` | Org management (membership, teams) for orgs you admin. | | |
| 27 | +| `gist` | Reserved for future Gists feature; non-functional today. | | |
| 28 | + | |
| 29 | +Scopes only **grant**; they never elevate. A `repo` scope on your | |
| 30 | +PAT cannot push to a repo you don't have write access to. | |
| 31 | + | |
| 32 | +## Creating a PAT | |
| 33 | + | |
| 34 | +Settings → Developer settings → Personal access tokens → "New | |
| 35 | +token". | |
| 36 | + | |
| 37 | +- **Note** — what is this token for? "ci-runner staging" beats | |
| 38 | + "test1". | |
| 39 | +- **Expiration** — pick the smallest tolerable; 90 days is a | |
| 40 | + reasonable default. "Never" is available but discouraged. | |
| 41 | +- **Scopes** — check only what you need. | |
| 42 | + | |
| 43 | +The token is displayed **once**. Copy it now; we cannot show it | |
| 44 | +to you again. If you lose it, revoke and re-create. | |
| 45 | + | |
| 46 | +## Token format | |
| 47 | + | |
| 48 | +Tokens are 40 characters of base32 with a `shp_` prefix: | |
| 49 | + | |
| 50 | +``` | |
| 51 | +shp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | |
| 52 | +``` | |
| 53 | + | |
| 54 | +The prefix is recognized by GitHub-style secret-scanning tools. | |
| 55 | +If you accidentally publish a token, secret scanners may notify | |
| 56 | +you (and us); revoke immediately. | |
| 57 | + | |
| 58 | +## Using a PAT | |
| 59 | + | |
| 60 | +- **Git over HTTPS:** username = your shithub username, password | |
| 61 | + = the PAT. See [HTTPS clone](./https.md). | |
| 62 | +- **API:** `Authorization: Bearer <token>` or `Authorization: | |
| 63 | + token <token>`. | |
| 64 | + | |
| 65 | +## Revoking | |
| 66 | + | |
| 67 | +Settings → Developer settings → Personal access tokens shows every | |
| 68 | +PAT on the account. Click "Revoke" — the token stops working | |
| 69 | +immediately. Anything using it will get `401`. | |
| 70 | + | |
| 71 | +If you suspect a token leaked, revoke first and investigate after. | |
docs/public/user/pull-requests.mdadded@@ -0,0 +1,78 @@ | ||
| 1 | +# Pull requests | |
| 2 | + | |
| 3 | +A pull request (PR) proposes merging one branch into another. | |
| 4 | +The PR is the conversation around the diff — review comments, | |
| 5 | +status checks, the merge itself. | |
| 6 | + | |
| 7 | +## Opening a PR | |
| 8 | + | |
| 9 | +1. Push a branch to the repo (or to your fork). | |
| 10 | +2. Visit the repo and click the "Compare & pull request" prompt | |
| 11 | + that appears, or go to Pull requests → "New pull request". | |
| 12 | +3. Pick **base** (where you want to merge into) and **compare** | |
| 13 | + (your branch). | |
| 14 | +4. Title + body. Markdown supported. Reference issues with | |
| 15 | + `Fixes #N` to auto-close on merge. | |
| 16 | + | |
| 17 | +The PR shows the file diff, commit list, and any pre-existing | |
| 18 | +status checks for the head commit. | |
| 19 | + | |
| 20 | +## Reviewing | |
| 21 | + | |
| 22 | +Open a PR → "Files changed" tab. | |
| 23 | + | |
| 24 | +- **Comment on a line:** click the `+` in the gutter. | |
| 25 | +- **Suggest a change:** in the comment, pick "Suggestion" — your | |
| 26 | + inline patch becomes a one-click commit the author can apply. | |
| 27 | +- **Submit a review:** "Review changes" → choose: | |
| 28 | + - **Comment** — observations, no verdict. | |
| 29 | + - **Approve** — green checkmark, counts toward the required- | |
| 30 | + reviewer threshold (if branch protection is on). | |
| 31 | + - **Request changes** — red X, blocks merge until dismissed or | |
| 32 | + re-reviewed. | |
| 33 | + | |
| 34 | +A reviewer can leave many inline comments and bundle them into | |
| 35 | +one review submission. | |
| 36 | + | |
| 37 | +## Merging | |
| 38 | + | |
| 39 | +Three merge methods (the maintainer picks per repo or per PR if | |
| 40 | +multiple are enabled): | |
| 41 | + | |
| 42 | +- **Merge commit** — the classic non-fast-forward merge with a | |
| 43 | + commit that has both parents. Preserves all PR commits as-is. | |
| 44 | +- **Squash and merge** — replaces every PR commit with one | |
| 45 | + squashed commit on the base branch. PR title/body becomes the | |
| 46 | + commit message. | |
| 47 | +- **Rebase and merge** — replays the PR's commits onto the base. | |
| 48 | + No merge commit; linear history. | |
| 49 | + | |
| 50 | +shithub detects merge conflicts up front and prevents merge | |
| 51 | +until the PR head is updated. Use the "Update branch" button to | |
| 52 | +merge or rebase the base into your branch (the maintainer picks | |
| 53 | +which strategy is allowed). | |
| 54 | + | |
| 55 | +## Branch protection gates | |
| 56 | + | |
| 57 | +If the base branch is protected, the merge button is disabled | |
| 58 | +until: | |
| 59 | + | |
| 60 | +- Required status checks pass (CI integrations report success). | |
| 61 | +- Required number of approvals collected. | |
| 62 | +- "Request changes" reviews resolved (dismiss or re-review). | |
| 63 | +- Conversations resolved (if "Require conversation resolution" is on). | |
| 64 | + | |
| 65 | +See [Branch protection & reviews](./branch-protection.md). | |
| 66 | + | |
| 67 | +## Draft PRs | |
| 68 | + | |
| 69 | +Open as draft to signal "not ready yet, but visible". Drafts | |
| 70 | +cannot be merged. Convert to "Ready for review" when you want | |
| 71 | +reviewers. | |
| 72 | + | |
| 73 | +## After merge | |
| 74 | + | |
| 75 | +- The head branch can be auto-deleted (account-level + per-repo | |
| 76 | + setting). | |
| 77 | +- Linked issues with `Fixes #N` close automatically. | |
| 78 | +- Watchers + subscribers get notifications. | |
docs/public/user/quickstart.mdadded@@ -0,0 +1,69 @@ | ||
| 1 | +# Quickstart | |
| 2 | + | |
| 3 | +This walks the simplest possible path: sign up, create a repo, | |
| 4 | +clone it, push a commit. About five minutes. | |
| 5 | + | |
| 6 | +## 1. Sign up | |
| 7 | + | |
| 8 | +Visit `/signup`. You'll need: | |
| 9 | + | |
| 10 | +- A working email address (a verification link is sent there). | |
| 11 | +- A username — letters, numbers, and dashes; not in our reserved | |
| 12 | + list (the form tells you if it is). | |
| 13 | +- A password — 12 characters minimum and not in the common-password | |
| 14 | + list. Argon2id is used to hash; we never store the plaintext. | |
| 15 | + | |
| 16 | +After you submit, the verification email arrives within a minute. | |
| 17 | +Click the link to activate the account. Until you do, you can sign | |
| 18 | +in but most write actions are gated. | |
| 19 | + | |
| 20 | +## 2. Create a repository | |
| 21 | + | |
| 22 | +Sign in and click "New repository" (or visit `/new`). | |
| 23 | + | |
| 24 | +- **Name** — alphanumerics and dashes; can't collide with your | |
| 25 | + existing repos. | |
| 26 | +- **Visibility** — public (anyone can read) or private (only you | |
| 27 | + + collaborators). | |
| 28 | +- **Initialize** — optionally add a README, a `.gitignore` (pick | |
| 29 | + from the templates), and a license. If you initialize, the repo | |
| 30 | + has commits immediately and you can clone right away. If you | |
| 31 | + don't, the repo is empty and you'll see push instructions. | |
| 32 | + | |
| 33 | +## 3. Clone the repo | |
| 34 | + | |
| 35 | +The repo page shows a "Code" dropdown with a clone URL. For | |
| 36 | +HTTPS you'll need a [personal access | |
| 37 | +token](./personal-access-tokens.md) — your account password does | |
| 38 | +not work for git operations. | |
| 39 | + | |
| 40 | +```sh | |
| 41 | +git clone https://shithub.example/<your-username>/<repo>.git | |
| 42 | +cd <repo> | |
| 43 | +``` | |
| 44 | + | |
| 45 | +When prompted for credentials, the username is your shithub | |
| 46 | +username and the password is the PAT. | |
| 47 | + | |
| 48 | +## 4. Push a commit | |
| 49 | + | |
| 50 | +```sh | |
| 51 | +echo "hello" >> README.md | |
| 52 | +git add README.md | |
| 53 | +git commit -m "say hello" | |
| 54 | +git push origin main | |
| 55 | +``` | |
| 56 | + | |
| 57 | +Refresh the repo page — your commit appears in the history. If | |
| 58 | +this is your first push, the repo's default branch is set to | |
| 59 | +whatever you pushed (usually `main`). | |
| 60 | + | |
| 61 | +## Where to go next | |
| 62 | + | |
| 63 | +- [Set up SSH](./ssh.md) so you don't have to enter a token every | |
| 64 | + push. | |
| 65 | +- [Open your first issue](./issues.md). | |
| 66 | +- [Open a pull request](./pull-requests.md) — branch, push, | |
| 67 | + compare. | |
| 68 | +- [Configure notifications](./notifications.md) so you don't get | |
| 69 | + buried. | |
docs/public/user/search.mdadded@@ -0,0 +1,59 @@ | ||
| 1 | +# Search | |
| 2 | + | |
| 3 | +Top-bar search opens a quick palette: type and you'll see matching | |
| 4 | +repos, users, issues, and code snippets. Pressing enter takes you | |
| 5 | +to the full search results page with filters. | |
| 6 | + | |
| 7 | +The full results page (`/search`) supports four scopes: | |
| 8 | + | |
| 9 | +- **Code** — file content + path matches across repos you can see. | |
| 10 | +- **Repositories** — repo names, descriptions, topics. | |
| 11 | +- **Issues + PRs** — across repos you can see. | |
| 12 | +- **Users** — usernames, display names. | |
| 13 | + | |
| 14 | +## Filters and operators | |
| 15 | + | |
| 16 | +The search bar supports a small grammar inspired by GitHub's: | |
| 17 | + | |
| 18 | +| Operator | Effect | | |
| 19 | +|---------------------------|-------------------------------------------------------| | |
| 20 | +| `repo:owner/name` | Restrict to one repo. | | |
| 21 | +| `org:name` | Restrict to repos owned by an org. | | |
| 22 | +| `user:name` | Restrict to a user's repos. | | |
| 23 | +| `path:src/` | Restrict by file path prefix. | | |
| 24 | +| `extension:go` | Restrict by file extension. | | |
| 25 | +| `language:go` | Restrict by detected language. | | |
| 26 | +| `author:name` | (issues/PRs) author. | | |
| 27 | +| `commenter:name` | (issues/PRs) someone commented. | | |
| 28 | +| `is:open` / `is:closed` | (issues/PRs) state. | | |
| 29 | +| `is:pr` / `is:issue` | Narrow to PR or issue. | | |
| 30 | +| `label:"good first issue"`| (issues/PRs) labelled. | | |
| 31 | +| `milestone:"v1.0"` | (issues/PRs) milestoned. | | |
| 32 | +| `created:2026-01-01..2026-04-01` | Date range (ISO 8601). | | |
| 33 | +| `-foo` | Negate. | | |
| 34 | + | |
| 35 | +Quoted strings are exact; unquoted strings are tokenized. | |
| 36 | + | |
| 37 | +## Visibility rules | |
| 38 | + | |
| 39 | +Search only ever returns results you would be allowed to see: | |
| 40 | + | |
| 41 | +- Public repos and their content/issues/PRs are searchable | |
| 42 | + without sign-in. | |
| 43 | +- Private repos require you to have read access (collaborator, | |
| 44 | + team member, owner). | |
| 45 | +- Search through someone else's PAT or session never reveals | |
| 46 | + results to your own queries — each user's search is scoped to | |
| 47 | + their own visibility. | |
| 48 | + | |
| 49 | +## Indexing latency | |
| 50 | + | |
| 51 | +- New repos are indexed within seconds of the first push. | |
| 52 | +- Subsequent pushes update the index within a few seconds. | |
| 53 | +- Issue + PR text is indexed inside the same transaction that | |
| 54 | + created the row — searchable immediately. | |
| 55 | + | |
| 56 | +If you push but the new content isn't appearing in search after | |
| 57 | +~30 seconds, the indexer is backed up; try the file directly via | |
| 58 | +the repo's tree view to confirm the data exists. Operators | |
| 59 | +monitor index lag; persistent staleness is an alert. | |
docs/public/user/ssh.mdadded@@ -0,0 +1,67 @@ | ||
| 1 | +# Cloning over SSH | |
| 2 | + | |
| 3 | +> **Status:** the SSH transport is planned but not yet shipped. | |
| 4 | +> Until it lands, use [HTTPS with a PAT](./https.md). The procedure | |
| 5 | +> below is what the SSH path will look like; the underlying server | |
| 6 | +> infrastructure (per-key authorization, command-locked sessions) | |
| 7 | +> already exists in the codebase. | |
| 8 | + | |
| 9 | +SSH lets you push and pull without re-entering credentials each | |
| 10 | +time. shithub authenticates each connection by SSH public key: | |
| 11 | +the key fingerprint maps to a user, and the session is locked to | |
| 12 | +the git protocol — you cannot get a shell. | |
| 13 | + | |
| 14 | +## 1. Generate an SSH key | |
| 15 | + | |
| 16 | +Skip this if you already have a key you're happy with | |
| 17 | +(`~/.ssh/id_ed25519.pub` typically). | |
| 18 | + | |
| 19 | +```sh | |
| 20 | +ssh-keygen -t ed25519 -C "you@example.com" | |
| 21 | +``` | |
| 22 | + | |
| 23 | +Accept the default path. Set a passphrase if you want belt-and- | |
| 24 | +braces; `ssh-agent` will remember it for the session. | |
| 25 | + | |
| 26 | +## 2. Copy the public key | |
| 27 | + | |
| 28 | +```sh | |
| 29 | +cat ~/.ssh/id_ed25519.pub | |
| 30 | +``` | |
| 31 | + | |
| 32 | +Copy the entire line, including the `ssh-ed25519` prefix and the | |
| 33 | +trailing comment. | |
| 34 | + | |
| 35 | +## 3. Add the key in shithub | |
| 36 | + | |
| 37 | +Settings → SSH and GPG keys → "New SSH key". Paste the key, give | |
| 38 | +it a label (e.g., "laptop"), save. | |
| 39 | + | |
| 40 | +The page shows the fingerprint shithub computed; verify it matches | |
| 41 | +what `ssh-keygen -l -f ~/.ssh/id_ed25519.pub` prints locally. | |
| 42 | + | |
| 43 | +## 4. Test the connection | |
| 44 | + | |
| 45 | +```sh | |
| 46 | +ssh -T git@shithub.example | |
| 47 | +``` | |
| 48 | + | |
| 49 | +You'll see a confirmation message. The `-T` disables PTY allocation; | |
| 50 | +shithub's SSH service refuses TTYs anyway. | |
| 51 | + | |
| 52 | +## 5. Clone with SSH | |
| 53 | + | |
| 54 | +```sh | |
| 55 | +git clone git@shithub.example:<owner>/<repo>.git | |
| 56 | +``` | |
| 57 | + | |
| 58 | +Subsequent pushes don't prompt — the agent presents the key, the | |
| 59 | +server matches the fingerprint to your account, and the session | |
| 60 | +is locked to `git-receive-pack` / `git-upload-pack`. | |
| 61 | + | |
| 62 | +## Removing or rotating a key | |
| 63 | + | |
| 64 | +Settings → SSH and GPG keys lists every key on the account with | |
| 65 | +its last-used timestamp. Remove a key the moment a device is lost | |
| 66 | +or decommissioned — the next `git push` from that device will be | |
| 67 | +rejected. | |
docs/public/user/webhooks.mdadded@@ -0,0 +1,118 @@ | ||
| 1 | +# Webhooks | |
| 2 | + | |
| 3 | +Webhooks send HTTP POSTs to your URL when something happens in a | |
| 4 | +repo (push, PR opened, issue commented, etc.). Configured at | |
| 5 | +Repository → Settings → Webhooks → "Add webhook". | |
| 6 | + | |
| 7 | +## Configuration | |
| 8 | + | |
| 9 | +- **Payload URL** — the HTTPS endpoint we POST to. HTTP is | |
| 10 | + rejected on production instances. | |
| 11 | +- **Content type** — `application/json` (default) or | |
| 12 | + `application/x-www-form-urlencoded`. | |
| 13 | +- **Secret** — used to HMAC-sign each delivery. We strongly | |
| 14 | + recommend setting one. The secret is stored AEAD-encrypted at | |
| 15 | + rest; you cannot retrieve it after creation, only replace it. | |
| 16 | +- **Events** — pick "Just push", "Send everything", or specific | |
| 17 | + events. | |
| 18 | +- **Active** — toggle without deleting. | |
| 19 | + | |
| 20 | +## Signature verification | |
| 21 | + | |
| 22 | +Each delivery includes: | |
| 23 | + | |
| 24 | +- `X-Shithub-Event: <event-name>` — e.g., `push`, `pull_request`. | |
| 25 | +- `X-Shithub-Delivery: <uuid>` — unique per delivery (idempotent). | |
| 26 | +- `X-Shithub-Signature-256: sha256=<hex>` — HMAC-SHA256 of the | |
| 27 | + raw body using your configured secret. | |
| 28 | + | |
| 29 | +**Always verify the signature before trusting the payload.** | |
| 30 | +Constant-time comparison; never compare with `==`. | |
| 31 | + | |
| 32 | +### Go | |
| 33 | + | |
| 34 | +```go | |
| 35 | +func verify(body []byte, sig, secret string) bool { | |
| 36 | + sig = strings.TrimPrefix(sig, "sha256=") | |
| 37 | + mac := hmac.New(sha256.New, []byte(secret)) | |
| 38 | + mac.Write(body) | |
| 39 | + expected := hex.EncodeToString(mac.Sum(nil)) | |
| 40 | + return hmac.Equal([]byte(sig), []byte(expected)) | |
| 41 | +} | |
| 42 | +``` | |
| 43 | + | |
| 44 | +### Python | |
| 45 | + | |
| 46 | +```python | |
| 47 | +import hmac, hashlib | |
| 48 | + | |
| 49 | +def verify(body: bytes, sig: str, secret: str) -> bool: | |
| 50 | + expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() | |
| 51 | + sig = sig.removeprefix("sha256=") | |
| 52 | + return hmac.compare_digest(sig, expected) | |
| 53 | +``` | |
| 54 | + | |
| 55 | +### Node.js | |
| 56 | + | |
| 57 | +```js | |
| 58 | +const crypto = require("crypto"); | |
| 59 | + | |
| 60 | +function verify(body, sig, secret) { | |
| 61 | + const expected = "sha256=" + crypto | |
| 62 | + .createHmac("sha256", secret) | |
| 63 | + .update(body) | |
| 64 | + .digest("hex"); | |
| 65 | + return crypto.timingSafeEqual( | |
| 66 | + Buffer.from(sig), | |
| 67 | + Buffer.from(expected), | |
| 68 | + ); | |
| 69 | +} | |
| 70 | +``` | |
| 71 | + | |
| 72 | +The body must be the **raw request body**, not the parsed JSON. | |
| 73 | +Frameworks that auto-parse will give you the wrong bytes. | |
| 74 | + | |
| 75 | +## Idempotency | |
| 76 | + | |
| 77 | +Use `X-Shithub-Delivery` as your idempotency key. We may retry a | |
| 78 | +delivery if your endpoint returns 5xx or times out, so processing | |
| 79 | +the same delivery twice should be safe in your system. | |
| 80 | + | |
| 81 | +## Retries | |
| 82 | + | |
| 83 | +A delivery is retried on: | |
| 84 | + | |
| 85 | +- Network error. | |
| 86 | +- 5xx response. | |
| 87 | +- Timeout (default 10s). | |
| 88 | + | |
| 89 | +Retry schedule: exponential backoff with jitter, up to ~6 retries | |
| 90 | +over ~24h. After 50 consecutive failures, the webhook **auto- | |
| 91 | +disables** to stop bombarding a broken endpoint. You'll see a | |
| 92 | +banner on the webhook config page; flip "Active" back on once | |
| 93 | +the endpoint is fixed. | |
| 94 | + | |
| 95 | +## Inspecting deliveries | |
| 96 | + | |
| 97 | +Webhook detail page → "Recent deliveries". Each row shows: | |
| 98 | + | |
| 99 | +- Event + delivery ID + timestamp. | |
| 100 | +- Request headers + (truncated) body we sent. | |
| 101 | +- Response status + headers + (truncated) body we got back. | |
| 102 | +- "Redeliver" — re-sends the original payload with the same | |
| 103 | + signature. | |
| 104 | + | |
| 105 | +Stored bodies are capped at 32 KiB (your endpoint can accept | |
| 106 | +bigger; we just don't keep more for the inspector). | |
| 107 | + | |
| 108 | +## SSRF defense | |
| 109 | + | |
| 110 | +shithub validates webhook URLs server-side: hostnames are | |
| 111 | +resolved, IPs are checked against a block-list (RFC1918, link- | |
| 112 | +local, loopback, multicast, 169.254.0.0/16, etc.), and the | |
| 113 | +request is dialed to the resolved IP — no following CNAMEs into | |
| 114 | +internal address space at delivery time. | |
| 115 | + | |
| 116 | +Operators of self-hosted instances can opt in to private | |
| 117 | +destinations via an `AllowedHosts` list — see | |
| 118 | +[self-host configuration](../self-host/configuration.md). | |