How Groundrule protects your code
This page describes the security controls in Groundrule, as the product implements them today. It covers workspace isolation, sign-in, sessions, tokens, connected apps, GitHub, and how your code is handled. Groundrule doesn’t claim any certification. For what data is kept and for how long, see What we store. For AI, see AI and your data.
Workspace isolation
Section titled “Workspace isolation”- Every workspace’s data is separated in the database by row-level security. Each table that holds workspace data carries the workspace’s ID, and the database itself only returns rows for the workspace a request is acting in.
- The application checks the same thing again. Workspace access is checked in code before every query, so a mistake in one layer is caught by the other.
- The workspace comes from your session or token, never from the request. A request can’t ask for another workspace’s data by naming it.
- Non-members get “not found”. Opening a workspace you don’t belong to returns “not found”, never “forbidden”, so workspace names aren’t revealed.
- Roles are enforced on the server. A role that can’t do something gets an error, for example “Your role (developer) can’t decide on proposals. Ask an admin.” Hiding a button is never the only control. See Roles and permissions.
Passwords
Section titled “Passwords”| Control | Detail |
|---|---|
| Hashing | scrypt with N=2^17, r=8, p=1 (128 MiB of memory per hash), a 16-byte random salt, and a 64-byte key. Each hash records its own parameters, so the cost can be raised later |
| Length | 10 to 128 characters, any characters, including spaces |
| Blocked passwords | Common passwords from breach lists, a single repeated character, and passwords that contain your email address |
| Comparison | Constant-time |
| Unknown emails | A failed sign-in takes the same time whether or not the email has an account, and says “That email and password don’t match.” |
| Forgotten passwords | Forgot password? shows the same “Check your inbox” page for any email, so it never reveals which emails have accounts |
Sessions
Section titled “Sessions”- A session is a random token in the
gr_sessioncookie. The cookie is HTTP-only (page scripts can’t read it), Secure (sent only over HTTPS), and SameSite=Lax. - Groundrule stores only a SHA-256 hash of the session token, never the token itself.
- A session lasts 30 days.
- Settings → Your account → Where you’re signed in lists your sessions by browser and operating system.
- Changing your password signs out every other session: “Password changed. Other sessions were signed out.”
- Resetting your password signs out every other session too, and Groundrule emails you that the password changed.
Email links
Section titled “Email links”Confirmation, sign-in and password-reset links each carry a random token. Groundrule stores only its hash.
| Link | Valid for | Uses |
|---|---|---|
| Sign-in link | 15 minutes | Once |
| Password reset | 30 minutes | Once |
| Email confirmation | 24 hours | Once |
Opening a link shows a page with a button (Confirm and continue, Sign in), so mail scanners that open links can’t use them up. A used or expired link shows Link expired.
Request forgery protection
Section titled “Request forgery protection”Any request that changes something and is signed in with a cookie must come from the Groundrule app’s own address. Groundrule checks the browser’s Origin header and refuses anything else: “Requests must come from the Groundrule app.” Connecting Notion, Google Docs, Confluence or GitHub uses a one-time state value in a short-lived cookie, so a callback from another browser or another site is refused.
Rate limits
Section titled “Rate limits”Repeated attempts are slowed down per email address and per IP address. When a limit is reached, the request is refused with a message such as “Too many imports. Wait a while and try again.”
| Action | Limit |
|---|---|
| Sign in with a password | 10 per email and 50 per IP address, per 15 minutes |
| Create an account | 3 per email and 20 per IP address, per hour |
| Email a sign-in link | 3 per email per 15 minutes, 20 per IP address per hour |
| Request a password reset | 3 per email and 20 per IP address, per hour |
| Resend a confirmation email | 3 per email and 20 per IP address, per hour |
| Open an email link | 30 per IP address per 15 minutes |
| Change your password | 10 per 15 minutes |
| AI requests (drafts, Describe a rule) | 60 per person and 300 per workspace, per hour |
| Document imports | 30 per person per hour |
| Proposals from the CLI and MCP | 30 per person per hour, 500 per workspace per day |
| Scan uploads | 60 per token per hour, 1,000 per workspace per day |
API and CLI tokens
Section titled “API and CLI tokens”| Control | Detail |
|---|---|
| Format | grt_ followed by 32 random bytes |
| Storage | Only a SHA-256 hash. The full token is shown once, when it is created |
| Display | The first 12 characters, so you can recognize it |
| Permissions | Reading the rulebook is always included. Upload scans and Propose rules are optional |
| What tokens can’t do | Change the rulebook, members or settings. Tokens work only for the CLI’s endpoints, and never with a cookie |
| Identity | A token acts as the person who created it, in one workspace |
| Expiry | 30, 90 or 365 days, or never. groundrule login creates a 90-day token |
| Revoking | Settings → API tokens. Admins can see and revoke everyone’s tokens; others, their own |
| Active tokens | Up to 25 per person per workspace |
The CLI saves credentials with file mode 600, in a folder with mode 700. It refuses to send a token over plain http, except to localhost: “Refusing to send credentials to … use https”. See Environment and files.
Connected apps
Section titled “Connected apps”- Notion, Google Docs and Confluence are connected through each app’s own OAuth consent screen, by an admin or platform admin.
- Tokens are encrypted at rest with AES-256-GCM, using a key held only by the Groundrule service. A copy of the database alone doesn’t reveal them.
- Tokens are never shown or returned, not even to admins: “Tokens are encrypted and never shown.”
- Access is read-only. Google Docs asks only to read documents, not to browse Drive. Confluence asks to read pages. Notion sees only pages shared with the integration.
- Pages are read only when someone imports them.
- Disconnect deletes the tokens, and revokes the access at the app where the app supports it.
GitHub
Section titled “GitHub”- Least privilege. The Groundrule GitHub App asks for read-only access to pull requests and metadata, on the repositories you choose on GitHub.
- Installations are tied to the installer. Groundrule saves an installation only when the person installing it can see it on GitHub, so a tampered installation ID can’t attach someone else’s repositories. One installation belongs to one workspace.
- Webhooks are verified. Every delivery’s HMAC-SHA256 signature is checked against the raw body, in constant time. A delivery with a wrong signature is refused.
- Each delivery is handled once. A replayed delivery is recognized by its ID and ignored.
- Bots are ignored, both in review imports and in
/groundrule rule. - Uninstalling the app on GitHub removes it from Groundrule.
See Rules from pull-request reviews.
Your code
Section titled “Your code”- Scans run on your computer or CI runner. The CLI never executes code from the repository, and reads configuration files as text.
- A scan uploads results, not source. At most 3 examples per rule, each with a one-line snippet of at most 200 characters. Snippets are never kept for security rules, have secrets redacted on your computer and again on the server, and can be turned off with
--no-snippets. See Scan repositories. - Uploaded documents are recognized by their content, not their name, and Word files are checked before they are unpacked, so a compressed file can’t expand without limit. The file itself is never stored.
- Checks written by AI are tested in isolation. A regular expression from Describe a rule runs in a separate sandbox with a time limit, and is probed for patterns that could run forever, before you see it.
- Logs never record authorization headers, cookies, or webhook signatures.