feat(demo): lock down identity, egress, and credential surfaces#185
feat(demo): lock down identity, egress, and credential surfaces#185TerrifiedBug merged 1 commit intomainfrom
Conversation
…mo mode Adds denyInDemo() middleware to mutations that would otherwise let a public demo user mint credentials, escalate privileges, configure outbound HTTP destinations, exfiltrate data, or stand up real fleet infrastructure. Routers covered: - user: changePassword, updateProfile, setupTotp, verifyAndEnableTotp, disableTotp - team: create, delete, rename, addMember, removeMember, updateMemberRole, lockMember, unlockMember, resetMemberPassword, updateRequireTwoFactor, updateAvailableTags, updateDefaultEnvironment, linkMemberToOidc, updateAiConfig, testAiConnection - settings: updateOidc/RoleMapping/TeamMappings, updateFleet, updateAnomalyConfig, testOidc, all backup/restore/storage mutations, updateScim, generateScimToken - environment: create, update, delete, generateEnrollmentToken, revokeEnrollmentToken - git-sync: retryAllFailed, retryJob - alert-channels: createChannel, updateChannel, deleteChannel, testChannel - webhook-endpoint: create, update, delete, toggleEnabled, testDelivery - secret: create, update, delete - certificate: upload, delete REST surface: - POST /api/agent/enroll: short-circuits with 403 in demo mode before any database lookup, so demo enrollment tokens are example-only. All existing router tests pass unchanged because vi.mock factories already expose denyInDemo as a passthrough. Adds a focused test confirming the enroll endpoint rejects in demo without touching the database.
Greptile SummaryThis PR extends the existing Confidence Score: 5/5Safe to merge — changes are mechanical middleware additions using an already-tested, well-understood guard with no logic or security issues found. All changes are additive (inserting an existing middleware before existing auth middleware), the denyInDemo() implementation is simple and correct, the REST guard short-circuits before any side effects, and the new test correctly verifies the DB-isolation property. No P0 or P1 issues found. No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant EnrollRoute as POST /api/agent/enroll
participant tRPC as tRPC Mutation
participant DenyInDemo as denyInDemo()
participant Auth as withTeamAccess / requireSuperAdmin
participant Handler as Procedure Handler
participant DB as PostgreSQL
Note over Client,DB: Demo mode ON (NEXT_PUBLIC_VF_DEMO_MODE=true)
Client->>EnrollRoute: POST {token, hostname}
EnrollRoute->>EnrollRoute: isDemoMode() → true
EnrollRoute-->>Client: 403 {error: disabled in demo}
Client->>tRPC: mutation (e.g. secret.create)
tRPC->>DenyInDemo: isDemoMode() → true
DenyInDemo-->>Client: FORBIDDEN
Note over Client,DB: Demo mode OFF (normal deployment)
Client->>EnrollRoute: POST {token, hostname}
EnrollRoute->>EnrollRoute: checkIpRateLimit()
EnrollRoute->>DB: environment.findMany + vectorNode.create
EnrollRoute-->>Client: 200 {nodeId, nodeToken}
Client->>tRPC: mutation (e.g. secret.create)
tRPC->>DenyInDemo: isDemoMode() → false
DenyInDemo->>Auth: pass through
Auth->>DB: team/membership lookup
Auth->>Handler: authorized
Handler->>DB: write
Handler-->>Client: result
Reviews (1): Last reviewed commit: "feat(demo): lock down identity, egress, ..." | Re-trigger Greptile |
… UI (#187) * feat(demo): add disabled badges and read-only fieldsets to demo settings UI Backend already rejects credential and identity mutations in demo mode (PR #185), but the UI still let users type into fields and click Save, producing toast errors at submit time. This adds frontend signaling so the read-only state is obvious before the user interacts. Adds three small primitives in src/components/demo-disabled.tsx: - DemoDisabledBadge -- inline "Demo" lock badge for card titles - DemoDisabledNotice -- amber info banner above a section - DemoDisabledFieldset -- wraps content in <fieldset disabled> + notice; passthrough outside demo mode Integrates them across the five settings surfaces flagged in the audit plus the per-environment enrollment token panel: - /settings/ai (AI provider + API key) - /settings/backup (S3 storage + manual backups + schedule) - /settings/auth (OIDC + IdP team mappings) - /settings/scim (SCIM provisioning + bearer token) - /environments/[id] (Git Integration tab + Agent Enrollment tab) For agent enrollment specifically: the Generate / Revoke buttons are disabled in demo, the Quick Start install snippets remain visible (now showing a clearly-fake "vf_enroll_demo_example_..." placeholder) so the demo still demonstrates the install flow without minting real tokens. The /api/agent/enroll endpoint already returns 403 in demo, so the placeholder cannot be redeemed. Components are no-ops outside demo mode (isDemoMode() short-circuits), so non-demo deployments are unchanged. * Update src/app/(dashboard)/settings/_components/backup-settings.tsx Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix(demo): repair backup-settings JSX after malformed Greptile suggestion The Greptile suggestion accepted on PR #187 dropped the function's `return (` wrapper and the outer `<div>` while leaving two opening `<DemoDisabledFieldset>` tags, producing a JSX parse error in CI. Restores the return wrapper and consolidates to a single `<DemoDisabledFieldset message=...>` (which already renders the notice internally via its `message` prop, matching the suggestion's intent). Also drops the now-unused DemoDisabledFieldset / DemoDisabledNotice imports flagged by no-unused-vars. --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Summary
Closes the demo-mode coverage gaps surfaced during a deep audit. PR #184 added
denyInDemo()to a handful of admin/service-account/alert-webhook mutations; this extends the guard to every mutation that could let a public demo user mint credentials, escalate privileges, exfiltrate data, configure outbound HTTP, or stand up real fleet infrastructure.Backend coverage added
Identity / privilege escalation
user.*self-edit: changePassword, updateProfile, setupTotp, verifyAndEnableTotp, disableTotpteam.*membership: create/delete/rename, addMember, removeMember, updateMemberRole, lockMember, unlockMember, resetMemberPassword, updateRequireTwoFactor, updateAvailableTags, updateDefaultEnvironment, linkMemberToOidcEgress / outbound HTTP / credential storage
team.updateAiConfig,team.testAiConnection(LLM API keys + base URL)settings.updateOidc*,settings.testOidc,settings.updateScim,settings.generateScimTokensettings.updateFleet,settings.updateAnomalyConfigsettings.createBackup,previewBackup,deleteBackup,restoreBackup,updateBackupSchedule,testS3Connection,updateStorageBackendenvironment.create/update/delete,generateEnrollmentToken,revokeEnrollmentTokengitSync.retryAllFailed,retryJobalertChannels.createChannel/updateChannel/deleteChannel/testChannelwebhookEndpoint.create/update/delete/toggleEnabled/testDeliverysecret.create/update/deletecertificate.upload/deleteREST surface
POST /api/agent/enrollshort-circuits with 403 in demo before any database lookup. Demo enrollment tokens are now example-only.Risk notes
denyInDemo()already exists insrc/trpc/init.tsand is mocked as a passthrough in every router test file from PR feat(demo): harden hosted demo against egress + identity mutations #184, so existing tests pass unchanged.Test plan
pnpm vitest run-- 256 files / 2532 tests passtsc --noEmitcleanFORBIDDENNEXT_PUBLIC_VF_DEMO_MODEis unset)