Skip to content

docs: RFC 0002 for explicit consent at signup - #1905

Open
rohanchkrabrty wants to merge 8 commits into
mainfrom
feat-rfc-separate-login
Open

docs: RFC 0002 for explicit consent at signup#1905
rohanchkrabrty wants to merge 8 commits into
mainfrom
feat-rfc-separate-login

Conversation

@rohanchkrabrty

Copy link
Copy Markdown
Contributor

Summary

  • Adds RFC 0002, proposing explicit consent at signup: a deployment declares its documents in app.consent config, the client sends the accepted document ids, and frontier writes an immutable user_consents record together with the new user in one transaction. With app.consent unset or disabled, nothing changes.
  • Adds a flow_intent field on AuthenticateRequest to separate login from signup. Consent is a signup-only requirement, and today both are the same request, so a login silently creates the account it should have refused and the consent check has nothing to key off before the OIDC redirect.
  • Specifies storage: one row per consent with document versions and URLs copied from config, no foreign key to users so records outlive account deletion, and BEFORE UPDATE/BEFORE DELETE triggers plus a create-only repository making them unmodifiable.
  • Specifies the user.consent_granted audit record fields, including why its actor has to be set explicitly rather than left to context enrichment on the skip-listed auth endpoints, and why it cannot be atomic with the consent write.
  • Records the accepted tradeoffs: no re-consent or withdrawal, three operator paths exempt from consent, and the account-enumeration oracle the login gate introduces.

@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview Aug 27, 2026 8:14pm

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Documentation
    • Added an RFC proposing explicit consent during signup.
    • Documents configurable consent requirements, consent capture across authentication flows, validation, auditing, and immutable consent records.
    • Covers the SDK signup experience, error handling, alternatives, limitations, and proposed implementation order.
    • Refined and clarified the proposal’s language without changing the described behavior.

Walkthrough

This RFC proposes explicit signup consent with configurable documents, flow intent propagation, consent validation, immutable consent records, audit events, and SDK checkbox support. It also defines errors, limitations, alternatives, references, and implementation order.

Changes

Explicit Signup Consent

Layer / File(s) Summary
Consent contracts and flow metadata
docs/rfcs/0002-explicit-consent-at-signup.md
Defines app.consent, the ListConsentDocuments endpoint, new AuthenticateRequest fields, FlowIntent, and consent metadata across OIDC redirects.
Signup and login enforcement
docs/rfcs/0002-explicit-consent-at-signup.md
Defines intent-based gates, consent resolution, user-creation validation, caller coverage, flow termination, and error mappings.
Consent records and audit events
docs/rfcs/0002-explicit-consent-at-signup.md
Specifies transactional consent persistence, immutable user_consents records, reporting queries, and audit events.
SDK behavior and implementation plan
docs/rfcs/0002-explicit-consent-at-signup.md
Describes SDK consent controls, alternatives, limitations, future work, references, and implementation order.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: 🟡 Moderate · up to 249af

This draft changes signup/login behavior and consent records but still leaves a post-commit audit-loss path, potentially unsafe configured links, and conflicting or incomplete authentication and error-handling rules. These could cause missing compliance records or broken and misleading signup flows, so the PR needs fixes or explicit owner acceptance before merge.

Suggested reviewers: amangit07, rohilsurana, shreyag02, whoabhisheksah

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coveralls

coveralls commented Aug 27, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 33112327895

Coverage remained the same at 49.168%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 40510
Covered Lines: 19918
Line Coverage: 49.17%
Coverage Strength: 15.74 hits per line

💛 - Coveralls

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1dc2fe21-4385-44c5-ac55-af0f437bbbc7

📥 Commits

Reviewing files that changed from the base of the PR and between 6bf5e75 and 20bfedd.

📒 Files selected for processing (1)
  • docs/rfcs/0002-explicit-consent-at-signup.md

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.

Comment thread docs/rfcs/0002-explicit-consent-at-signup.md Outdated
Comment on lines +235 to +237
This also fixes something unrelated. `applyOIDC` never calls `consumeFlow`, so OIDC flow rows sit
around until the expiry cron while mail OTP rows are deleted on use. That is hard to justify once
those rows hold consent.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- repository convention files ---'
find /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8 -maxdepth 2 -type f -name '*.md' -print
printf '%s\n' '--- RFC target ---'
sed -n '210,255p' docs/rfcs/0002-explicit-consent-at-signup.md
printf '%s\n' '--- passkey flow symbols ---'
rg -n -C 12 'finishPassKeyRegisterMethod|consumeFlow|PassKeyRegister' --glob '*.go' .

Repository: raystack/frontier

Length of output: 12894


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- passkey registration implementation ---'
sed -n '504,632p' core/authenticate/service.go
printf '%s\n' '--- callback dispatch and user creation path ---'
sed -n '633,735p' core/authenticate/service.go
printf '%s\n' '--- flow repository delete contract ---'
rg -n -C 10 'type .*Flow.*Repository|func .*Delete\\(|DeleteExpiredFlows|flowRepo' core/authenticate --glob '*.go'
printf '%s\n' '--- scoped repository learnings ---'
cat /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8/learnings/core.md
cat /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8/learnings/repo-wide.md

Repository: raystack/frontier

Length of output: 6887


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- flow cleanup callers ---'
rg -n -C 8 'consumeFlow|applyPasskey|finishPassKeyRegisterMethod' core/authenticate --glob '*.go'
printf '%s\n' '--- applicable repository learnings ---'
sed -n '1,220p' /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8/learnings/core.md
sed -n '1,220p' /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8/learnings/repo-wide.md

Repository: raystack/frontier

Length of output: 12334


Apply flow cleanup to passkey registration too.

finishPassKeyRegisterMethod updates the user and returns without calling consumeFlow. The passkey callback path has no alternate cleanup owner, so successful registration leaves consent metadata until expiry. Consume the flow on success and terminal failure, or document and test another cleanup owner.

🧰 Tools
🪛 LanguageTool

[style] ~236-~236: To elevate your writing, try using a synonym here.
Context: ...il OTP rows are deleted on use. That is hard to justify once those rows hold consent...

(HARD_TO)

Comment on lines +256 to +260
`StartFlow` guesses signup from login for passkey by looking the user up
(`core/authenticate/service.go:220`), and `finishPassKeyLoginMethod` calls `getOrCreateUser`, so a
passkey login can create an account today. The intent replaces the guess: signup picks
`startPassKeyRegisterMethod`, login picks `startPassKeyLoginMethod`, and unspecified keeps the
guess so nothing existing breaks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Define explicit passkey login behavior.

The RFC says that explicit FLOW_INTENT_LOGIN selects the passkey login path, but the table also calls a first-time passkey login a signup. Scope that statement to FLOW_INTENT_UNSPECIFIED only.

For FLOW_INTENT_LOGIN, finishPassKeyLoginMethod must return ErrLoginUserNotFound when no user exists. It must not create a user or write consent. Otherwise the stated login invariant remains false.

Also applies to: 354-359

Comment on lines +304 to +306
`getOrCreateUser` takes the flow, which is a signature change. The flow carries both the intent
and the consent, so one parameter serves both gates, and the nil-safe accessors mean
`authenticateWithPassthroughHeader`, which has no flow, passes nil and needs no branch of its own.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

printf '%s\n' '--- repository conventions ---'
find /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8 -type f -name '*.md' -print
printf '%s\n' '--- scoped convention headers ---'
head -5 /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8/*/*.md 2>/dev/null || true
printf '%s\n' '--- RFC lines 280-375 ---'
sed -n '280,375p' docs/rfcs/0002-explicit-consent-at-signup.md
printf '%s\n' '--- RFC symbols and related terms ---'
rg -n -C 3 'getOrCreateUser|authenticateWithPassthroughHeader|ResolveAll|unspecified|nil-safe|finishPassKeyRegisterMethod|consumeFlow|consent|intent' docs/rfcs/0002-explicit-consent-at-signup.md

Repository: raystack/frontier

Length of output: 38820


🏁 Script executed:

printf '%s\n' '--- RFC flow accessors and enforcement ---'
sed -n '215,265p' docs/rfcs/0002-explicit-consent-at-signup.md
printf '%s\n' '--- RFC implementation plan and limitations ---'
sed -n '588,670p' docs/rfcs/0002-explicit-consent-at-signup.md
printf '%s\n' '--- current authenticate implementation ---'
fd -t f -i 'service.go' | while read -r f; do
  if rg -q 'getOrCreateUser|authenticateWithPassthroughHeader|finishPassKey' "$f"; then
    printf '%s\n' "--- $f ---"
    rg -n -C 5 'getOrCreateUser|authenticateWithPassthroughHeader|finishPassKey' "$f"
  fi
done

Repository: raystack/frontier

Length of output: 11649


🏁 Script executed:

printf '%s\n' '--- complete current getOrCreateUser and nearby callers ---'
sed -n '780,845p' core/authenticate/service.go
printf '%s\n' '--- all current getOrCreateUser references ---'
rg -n -C 8 'getOrCreateUser|authenticateWithPassthroughHeader' core internal
printf '%s\n' '--- RFC references to passthrough and flow argument ---'
rg -n -C 10 'Passthrough|passthrough|takes the flow|flow presence|no flow|nil' docs/rfcs/0002-explicit-consent-at-signup.md

Repository: raystack/frontier

Length of output: 17044


Distinguish a nil flow from an unspecified-intent flow.

getOrCreateUser must skip consent enforcement only when flow == nil. The nil-safe accessors return FlowIntentUnspecified and no consent for both a nil flow and a non-nil flow with unspecified intent. If enforcement uses only those values, passthrough provisioning can be rejected, or an unspecified-intent signup can bypass ResolveAll. Add separate tests for both paths.

Comment on lines +337 to +341
They map to `CodeNotFound`, `CodeAlreadyExists` and `CodeFailedPrecondition`, returned from both
`Authenticate` and `AuthCallback`. `AuthCallback` maps a fixed list of errors to `InvalidArgument`
and everything else to `Internal` (`internal/api/v1beta1connect/authenticate.go:117`), so all
three have to be added to that list or they surface as 500s. `FailedPrecondition` is what lets a
client tell a consent rejection apart from a bad code or an expired flow and ask again.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- repository convention scopes ---'
head -5 /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8/*/*.md 2>/dev/null || true
printf '%s\n' '--- changed RFC section ---'
sed -n '320,350p' docs/rfcs/0002-explicit-consent-at-signup.md
printf '%s\n' '--- related RFC section ---'
sed -n '510,535p' docs/rfcs/0002-explicit-consent-at-signup.md
printf '%s\n' '--- handler definitions and call sites ---'
rg -n -C 8 'AuthCallback|magicLinkHandler|CodeNotFound|CodeAlreadyExists|CodeFailedPrecondition|CodeInvalidArgument' internal docs/rfcs/0002-explicit-consent-at-signup.md

Repository: raystack/frontier

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -eu
log=/tmp/coderabbit-shell-logs/shell-output-UFZttL
printf '%s\n' '--- relevant convention files ---'
find /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8 -type f -name '*.md' -print
printf '%s\n' '--- RFC excerpts ---'
sed -n '330,345p' docs/rfcs/0002-explicit-consent-at-signup.md
sed -n '520,532p' docs/rfcs/0002-explicit-consent-at-signup.md
printf '%s\n' '--- AuthCallback implementation ---'
sed -n '80,145p' internal/api/v1beta1connect/authenticate.go
printf '%s\n' '--- magicLinkHandler definitions and uses ---'
rg -n -C 12 'func .*magicLinkHandler|magicLinkHandler|AuthCallback' --glob '*.go' .

Repository: raystack/frontier

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- scoped repository learnings ---'
cat /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8/learnings/internal-api-v1beta1connect.md
printf '%s\n' '--- exact frontend handler matches ---'
rg -n -C 15 'magicLinkHandler|status === 400|redirectSignup|redirectLogin' . --glob '!proto/v1beta1/frontier.pb.go'
printf '%s\n' '--- auth error declarations and service propagation ---'
rg -n -C 10 'ErrLoginUserNotFound|ErrSignupUserExists|ErrConsentRequired|FinishFlow|AuthCallback' internal core --glob '*.go'
printf '%s\n' '--- RFC surrounding references ---'
sed -n '300,345p' docs/rfcs/0002-explicit-consent-at-signup.md
sed -n '500,535p' docs/rfcs/0002-explicit-consent-at-signup.md

Repository: raystack/frontier

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- Authenticate handler ---'
sed -n '1,100p' internal/api/v1beta1connect/authenticate.go
printf '%s\n' '--- authentication service error declarations and flow paths ---'
rg -n -C 8 'ErrLoginUserNotFound|ErrSignupUserExists|ErrConsentRequired|func \(s Service\) StartFlow|func \(s Service\) applyMailOTP|func \(s Service\) applyOIDC|func \(s Service\) getOrCreateUser' core/authenticate --glob '*.go'
printf '%s\n' '--- SDK client and dependency contracts ---'
rg -n -C 8 'HttpErrorResponse|createConnectTransport|create.*Client|FrontierServiceQueries|connect-web|connect-query' web/sdk web --glob 'package.json' --glob '*.ts' --glob '*.tsx'
printf '%s\n' '--- package versions ---'
find web -maxdepth 3 -type f \( -name 'package.json' -o -name 'pnpm-lock.yaml' -o -name 'yarn.lock' -o -name 'package-lock.json' \) -print

Repository: raystack/frontier

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- SDK package manifests ---'
find web -type f \( -name 'package.json' -o -name 'pnpm-lock.yaml' -o -name 'yarn.lock' -o -name 'package-lock.json' \) -print
printf '%s\n' '--- magic-link implementation ---'
sed -n '1,115p' web/sdk/client/views/auth/magic-link/magic-link-view.tsx
printf '%s\n' '--- SDK transport setup ---'
rg -n -C 10 'createConnectTransport|createGrpcWebTransport|createConnectQuery|TransportProvider|ConnectError|HttpErrorResponse' web/sdk --glob '*.ts' --glob '*.tsx'
printf '%s\n' '--- auth service relevant methods ---'
sed -n '230,370p' core/authenticate/service.go
printf '%s\n' '--- auth error declarations ---'
rg -n -C 3 'ErrLoginUserNotFound|ErrSignupUserExists|ErrConsentRequired' core internal --glob '*.go'

Repository: raystack/frontier

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- exact magic-link handler ---'
sed -n '1,110p' web/sdk/client/views/auth/magic-link/magic-link-view.tsx
printf '%s\n' '--- exact transport and error helper ---'
rg -l 'createConnectTransport|TransportProvider|function handleConnectError|const handleConnectError|HttpErrorResponse' web/sdk --glob '*.ts' --glob '*.tsx' \
  | while read -r f; do echo "--- $f"; rg -n -C 12 'createConnectTransport|TransportProvider|function handleConnectError|const handleConnectError|HttpErrorResponse' "$f"; done
printf '%s\n' '--- tracked dependency manifests ---'
git ls-files | grep -E '(^|/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$' | head -40
printf '%s\n' '--- Connect error handling conventions ---'
sed -n '1,180p' web/sdk/utils/error.ts 2>/dev/null || true

Repository: raystack/frontier

Length of output: 11052


Preserve the Connect error codes end to end.

AuthCallback maps every error in its fixed list to CodeInvalidArgument; errors outside the list become CodeInternal. Map ErrLoginUserNotFound, ErrSignupUserExists, and ErrConsentRequired to their intended codes instead of adding them to the existing branch.

MagicLinkView uses @connectrpc/connect-query, but its catch block checks err instanceof Response and status === 400. Handle the returned Connect error codes, including NotFound, AlreadyExists, and FailedPrecondition, or define one explicit transport mapping.

Comment on lines +530 to +532
The OIDC rejections arrive at `AuthCallback`, which is the callback page rather than the view that
started the flow, and that page has no error UI. Redirecting back to the originating view with an
error param is the smaller change, rendering it in place is the other option, and this is undecided.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Define the OIDC callback error path before implementation.

When the callback rejects an OIDC signup, the callback page has no error UI and the RFC leaves redirection undecided. The user has no documented way to return to the sign-up view and retry.

Specify the redirect target, an allowlisted non-sensitive error code, and how the originating view renders the error.

Shorter sentences and plainer wording throughout, and cut the paragraphs
that restated a decision already made earlier in the document. No design
changes: every decision, file reference and verified claim is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4f80a22e-3585-4be4-9eb5-ef22fb91b974

📥 Commits

Reviewing files that changed from the base of the PR and between 20bfedd and 6349560.

📒 Files selected for processing (1)
  • docs/rfcs/0002-explicit-consent-at-signup.md

Included review availability: Your plan provides up to 2 included reviews per hour; 0 remain after this review.

Comment on lines +96 to +99
Every document in the map is required at signup, so there is no per-document `required` flag. An
optional document would need withdrawal, which is out of scope. The cost is that adding a document
id breaks signup for clients still sending the old list, so a new document ships with the client
release that sends it. Version bumps are safe, since the client sends only ids.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Pin the document snapshot to the flow.

StartFlow stores only accepted_document_ids, but Grant later copies version and url from the current configuration. If configuration changes during an OIDC redirect, or an old client reaches a newer configuration, the immutable record can claim consent to a version the user did not see. This makes the consent evidence inaccurate. Store the resolved document snapshot or a configuration revision in Flow.Metadata, then make Grant use that snapshot. Remove the claim that version bumps are safe.

Proposed contract change
-  "accepted_document_ids": ids,
+  "documents": resolvedDocuments,

Also applies to: 184-189, 356-357, 519-522

Comment on lines +103 to +105
`enabled` switches the whole feature. Absent or false, frontier behaves as it does today, and
`accepted_document_ids` is ignored rather than rejected, so one client build works against both
kinds of deployment. True with no documents fails at boot rather than silently disabling itself.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make disabled-mode handling consistent.

When consent is disabled, the RFC says accepted_document_ids is ignored. It also says that login requests containing those IDs are rejected. A shared client can therefore fail login against a disabled deployment. Define which rule applies. If one client must work with both deployment modes, ignore the field while consent is disabled and apply the login rejection only when consent is enabled.

Also applies to: 134-136

@rohilsurana

Copy link
Copy Markdown
Member

Few points as we discussed -

  • remove metadata from consent table. If needed we will ad that in future.
  • remove GIN index from documents field of consent table. We don't really need to access it in that way.
  • consent record should store auth_strategy instead of auth_method.
  • audit record for this event will have schema.PlatformOrgId as the org_id
  • add an endpoint to fetch the list of documents on UI

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f4716e0-66c8-40e4-a967-5a41169a19d1

📥 Commits

Reviewing files that changed from the base of the PR and between 6349560 and 249af92.

📒 Files selected for processing (1)
  • docs/rfcs/0002-explicit-consent-at-signup.md

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.

Comment on lines +116 to +125
`ListConsentDocuments`, unauthenticated, so a sign-up view can render the documents it is asking the
user to accept:

```proto
message ConsentDocument {
string id = 1;
string title = 2;
string version = 3;
string url = 4;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -eu

printf '%s\n' '--- applicable convention files ---'
find /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8 -maxdepth 2 -type f -name '*.md' -print

printf '%s\n' '--- RFC structure and URL-related text ---'
ast-grep outline docs/rfcs/0002-explicit-consent-at-signup.md
rg -n -C 4 'URL|url|HTTPS|https|parse|scheme|ListConsentDocuments|ConsentDocument|SDK|link' docs/rfcs/0002-explicit-consent-at-signup.md

printf '%s\n' '--- SDK consent/document references ---'
rg -n -C 3 'ListConsentDocuments|ConsentDocument|consent.*document|document.*consent|href=|window\.location' web internal core api docs --glob '*.{ts,tsx,go,md,proto}' 2>/dev/null || true

Repository: raystack/frontier

Length of output: 50375


Security Misconfiguration (CWE-319): Cleartext Transmission of Sensitive Information

Reachability: External · Exploitability: Moderate

Allow only HTTPS document URLs.

The RFC accepts any parseable URL and exposes it through ListConsentDocuments for the SDK’s user-facing link. Require the https scheme during boot validation before publishing the document set.

Suggested validation contract
- URLs must parse
+ URLs must parse and use the https scheme

Comment on lines +448 to +449
| `OrgID` | `schema.PlatformOrgID` |
| `IdempotencyKey` | empty. It is nullable, and there is nothing to deduplicate |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🔎 Supported by static analysis

🏁 Script executed:

printf '%s\n' '--- repository conventions ---'
find /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8 -maxdepth 2 -type f -name '*.md' -print
printf '%s\n' '--- RFC lines 420-475 ---'
sed -n '420,475p' docs/rfcs/0002-explicit-consent-at-signup.md
printf '%s\n' '--- audit and idempotency references ---'
rg -n -C 3 'Create|IdempotencyKey|consent_granted|audit|outbox|reconcil|deduplic' docs/rfcs/0002-explicit-consent-at-signup.md

Repository: raystack/frontier

Length of output: 12711


🏁 Script executed:

printf '%s\n' '--- relevant learning notes ---'
cat /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8/learnings/internal-store-postgres.md
printf '%s\n' '--- transaction design ---'
sed -n '295,335p' docs/rfcs/0002-explicit-consent-at-signup.md
printf '%s\n' '--- audit definitions and repository calls ---'
rg -n -C 5 'type AuditRecord|AuditRecordRepository|func .*Create|IdempotencyKey|idempotency_key|CREATE TABLE.*audit|audit_records' --glob '*.go' --glob '*.sql' --glob '*.md' .

Repository: raystack/frontier

Length of output: 50374


🏁 Script executed:

printf '%s\n' '--- audit-related files ---'
fd -i 'audit|auditrecord' . --type f | head -80
printf '%s\n' '--- exact audit repository declarations ---'
rg -l 'type .*Audit|AuditRecordRepository|IdempotencyKey|idempotency_key' --glob '*.go' --glob '*.sql' core internal pkg | head -80
printf '%s\n' '--- audit table/index definitions ---'
rg -n -C 4 'CREATE TABLE.*audit_records|CREATE UNIQUE INDEX.*audit|idempotency_key|target.*json|occurred_at' internal/store/postgres/migrations --glob '*.sql'

Repository: raystack/frontier

Length of output: 11523


🏁 Script executed:

printf '%s\n' '--- postgres audit record repository ---'
sed -n '1,260p' internal/store/postgres/audit_record_repository.go
printf '%s\n' '--- audit record model and SQL mapping ---'
sed -n '1,260p' internal/store/postgres/audit_record.go
sed -n '1,220p' core/auditrecord/models/models.go
printf '%s\n' '--- audit repository interfaces and service ---'
sed -n '1,220p' core/auditrecord/auditrecord.go
sed -n '1,240p' core/auditrecord/service.go
printf '%s\n' '--- audit migration table and indexes ---'
sed -n '23,78p' internal/store/postgres/migrations/20250901054744_create_audits_table.up.sql

Repository: raystack/frontier

Length of output: 29445


Make the consent audit event durable and idempotent.

The post-commit AuditRecordRepository.Create call can fail after signup succeeds, leaving no user.consent_granted event. An empty IdempotencyKey maps to NULL, and the audit table permits multiple NULL keys. The non-unique target index does not prevent duplicates. Use a transactional outbox or a retry and reconciliation path keyed by the consent record ID.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants