Repository navigation
Conversation
… (TECH-789) Every hypercerts-lexicon record certified.app writes (app.certified.*, org.hypercerts.*, org.hyperboards.*) now carries an app.certified.signature.defs#createdVia entry, signed per the ATProtocol Attestation Spec: secp256k1 over the CID of the record plus $sig bound to the repository DID. app.bsky.* and org.impactindexer.* stay unsigned. - Signing points: the xrpc proxy (own-repo create/put), groupServiceFetch (every CGS create/put, signed for the group DID) and the first-sign-in profile seed. Signing fails open and never blocks a write. - The group activity route used a raw fetch to CGS; it now goes through groupServiceFetch so group activities are signed too. - /.well-known/did.json serves the per-host did:web document (200 when a key is set, 404 when disabled, 500 for a malformed key). - Same-session getRecord/listRecords return lexToJson output so $bytes survives the client edit round trip; legacy blobs keep their shape. - The key is CREATED_VIA_SIGNING_KEY plus CREATED_VIA_DID, which must match did:web:<PUBLIC_URL host>. Unset means signing is off. - A contract test pins the signing points; scripts/verify-created-via.mjs verifies a stored record the way a third party would. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. Finishing Touches
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. Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
…XSS, TECH-796) prosemirror-view 1.41.8 rebuilt the data-pm-slice paste context without running the schema's attribute checks (GHSA-c8x8-7fp4-3x9w, fixed in 1.42.3). @tiptap/core <= 3.30.4 also allowed __proto__ attribute injection into the DOM (GHSA-cp6q-959q-f8rh). Both sit on the editor's paste path. - @tiptap/* 3.23.4 -> 3.32.0 (pins @tiptap/pm 3.32.0) - prosemirror-view 1.41.8 -> 1.42.8, prosemirror-model 1.25.6 -> 1.25.12 - overrides floors for prosemirror-view and @tiptap/core so a lockfile regeneration cannot fall back to a vulnerable version - vitest guard asserting the installed versions stay above the floors npm audit --omit=dev no longer reports any prosemirror or tiptap advisory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
next 16.2.6 is in the range of about 20 Next.js advisories (9.3.4-canary.0 - 16.4.0), including Critical unauthenticated RCE in the Image Optimization API (AVIF and SVG), which the app uses through next/image, plus SSRF in Image Optimization and rewrites, a middleware/proxy bypass under Turbopack, and Server Action DoS. Only next is bumped. It also brings sharp 0.35.5, which clears that High advisory. eslint-config-next stays at 16.2.6: 16.4.3 pulls in eslint-plugin-react-hooks 7.1, whose new rules raise 17 lint errors in unrelated code, so it gets a separate cleanup. npm audit --omit=dev no longer reports next or sharp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
isAllowedEmbedHost checked only the hostname, so a URL such as javascript://www.youtube.com/... passed and reached <iframe src> in both the editor node view and the read-only LeafletDocument renderer. React 19 currently rewrites javascript: URLs in src, so this was not exploitable, but the guard should not rely on that. http: embeds are also rejected now. The CSP frame-src already allows only the https provider origins, so they could never render anyway. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mount With next 16.4.3 the /dev/preview smoke specs failed with "Failed to load user profile: Profile not found". StrictMode's dev-only effect re-run called MockFetchProvider's cleanup, which restored the real window.fetch. The children's re-run fetch effects then hit the real /api/resolve-did instead of the fixtures. The restore is now deferred to the next tick and cancelled when the effect re-runs, so the mock survives the StrictMode remount and a real unmount still restores the real fetch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- postcss override and devDependency ^8.5.10 -> ^8.5.23. The override was forcing next's own pinned postcss 8.5.23 down to 8.5.15, which kept postcss (GHSA-r28c-9q8g-f849, GHSA-fxqj-rqcc-2cmp), nanoid and source-map-js flagged. Resolves to postcss 8.5.32. - undici 6.24.1 -> 6.29.1 (lockfile only, within @atproto-labs/fetch-node's ^6.14.1). The handle resolver fetches attacker-chosen domains, so the DoS, header injection and TLS advisories were reachable. - resend 6.10 -> 6.35, which drops svix and its vulnerable uuid. npm audit --omit=dev: 24 -> 16 findings, all of them zod <4.6.6 (GHSA-mrhx-c7xc-m3w5) through @atproto/*, which has no zod 3 fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the standard nonce-based "strict CSP" alongside the existing policy, as Content-Security-Policy-Report-Only, so violations are reported without blocking anything: script-src 'nonce-<per request>' 'strict-dynamic' https: 'unsafe-inline'; object-src 'none'; report-uri /api/csp-report; report-to csp-endpoint `https:` and 'unsafe-inline' are fallbacks for pre-CSP3 browsers; browsers that understand nonces ignore them. - src/lib/security/csp.ts is now the single source for both policies. The enforced policy is unchanged (a test pins it to the previous strings). - src/proxy.ts generates a nonce per page request (prefetches skipped) and sets the policy on the request, where Next.js reads it to stamp its own scripts, and on the response. - The root layout reads the nonce and passes it to next-themes for its inline anti-flash script. Reading request headers renders every page per request, which nonces require. The 19 previously static pages are now dynamic; only metadata files (icons, manifest, robots, sitemap) stay static. - /welcome lost its hourly ISR, so its featured profiles and network counts now sit in an hourly unstable_cache instead of being fetched on every visit. (`use cache` needs Cache Components, whose static shells can't carry a nonce.) - /api/csp-report logs both report formats as `[csp-report]` lines with query strings stripped (the OAuth callback URL carries a one-time code), rate-limited per IP. - e2e/csp.spec.ts fails on any CSP violation across the public routes and the editor/profile/feed preview surfaces, and checks the nonce is fresh per request. Verified against a production build (next start): zero violations on 11 routes including /home, /create and a record page; an injected onerror handler is reported (negative control). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r name On Vercel, the proxy's `content-security-policy-report-only` request override never reached the renderer: staging served every script without a nonce, so the report-only policy flagged all of them (locally under `next start` it worked). Set the request copy as `content-security-policy`, the form the Next.js guide uses. Request headers are only input to Next.js; the browser still gets the policy as Content-Security-Policy-Report-Only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Vercel, the enforced Content-Security-Policy from next.config.ts headers also reached the page render as a request header and took precedence over the proxy's. Next.js parsed that nonce-less policy and stamped no nonces, so staging's report-only policy flagged every script. `next start` doesn't merge headers this way, which is why local runs were clean. The proxy now sends both policies per request with the same nonce: - Content-Security-Policy: the previous directives and host allowlist, plus the nonce. Browsers ignore 'unsafe-inline' once a nonce is present, so injected inline scripts and inline event handlers are now blocked, not just reported. - Content-Security-Policy-Report-Only: the strict 'strict-dynamic' policy, unchanged. next.config.ts no longer sets a CSP for pages. API routes, which skip the proxy, get a static `default-src 'none'; frame-ancestors 'none'`. The e2e spec checks both headers share the nonce, and that an injected onerror handler doesn't run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The strict policy (script-src 'nonce-...' 'strict-dynamic', CSP2 fallbacks https: 'unsafe-inline', object-src 'none') becomes the one enforced Content-Security-Policy and the host-allowlist policy goes. Scripts now run only with the per-request nonce or when a nonced script loads them; 'self' and the Vercel script hosts are no longer trusted on their own. report-uri and report-to stay, so blocks keep reaching /api/csp-report. The e2e spec now also covers /oauth/callback, /embed/board and the activity-edit, profile-org, profile-edit, settings and workspace preview surfaces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ned out Signed out, OrgProvider returned before fetching without clearing isLoading, so every page waiting on the org list sat on its spinner forever instead of showing the sign-in prompt. useOrgCreationLimit had the same hang on its own since c749825: its render-time reset only fires when the did|orgsLoading key changes, so with the org list already settled at mount, isChecking stayed true. It now reports isChecking only while there is a signed-in DID to check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On production the graph took about 19s to appear: about 6s of indexer pages, then about 12s resolving ~240 DIDs in five 50-DID requests sent one after another, and only then the first draw. - useEndorsementGraph shows the graph as soon as its structure and the actor-profile index data are in, then fills handles and avatars into the same node objects (react-force-graph keeps layout positions on them, so the layout doesn't restart). - The resolve-dids batcher sends up to three chunks of a flush at once. The server charges its rate limit per identity, so the volume is unchanged. After a 429, remaining chunks degrade to null instead of being sent into the cooldown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kzoeps
self-requested a review
October 29, 2026 12:03
Kzoeps
approved these changes
Oct 29, 2026
holkexyz
marked this pull request as ready for review
October 29, 2026 12:26
| const [, repo, collection, rkey] = match | ||
|
|
||
| const repoDoc = await resolveDidDoc(repo) | ||
| const pds = repoDoc.service?.find((s) => s.id === "#atproto_pds")?.serviceEndpoint |
There was a problem hiding this comment.
verify-created-via.mjs only finds a PDS service whose id is exactly #atproto_pds. A valid DID document can use the full reference, such as did:plc:abc#atproto_pds. Those records fail with “no #atproto_pds service” before verification starts. Accept both forms, as resolvePdsUrl already does.
Suggested change
| const pds = repoDoc.service?.find((s) => s.id === "#atproto_pds")?.serviceEndpoint | |
| const pds = repoDoc.service?.find( | |
| (s) => s.id === "#atproto_pds" || s.id === `${repo}#atproto_pds`, | |
| )?.serviceEndpoint |
Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/verify-created-via.mjs
Line: 82
Comment:
**Full service references fail**
`verify-created-via.mjs` only finds a PDS service whose `id` is exactly `#atproto_pds`. A valid DID document can use the full reference, such as `did:plc:abc#atproto_pds`. Those records fail with “no #atproto_pds service” before verification starts. Accept both forms, as `resolvePdsUrl` already does.
```suggestion
const pds = repoDoc.service?.find(
(s) => s.id === "#atproto_pds" || s.id === `${repo}#atproto_pds`,
)?.serviceEndpoint
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
svc-agent
pushed a commit
that referenced
this pull request
Oct 29, 2026
The verify script only matched a service id of exactly "#atproto_pds". DID
documents may also use the full reference ("did:plc:...#atproto_pds"), and
those records failed with "no #atproto_pds service" before verification
started. Accept both forms; the app's resolvePdsUrl already does.
Found by Greptile on #254.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
svc-agent
pushed a commit
that referenced
this pull request
Oct 29, 2026
The verify script only matched a service id of exactly "#atproto_pds". DID
documents may also use the full reference ("did:plc:...#atproto_pds"), and
those records failed with "no #atproto_pds service" before verification
started. Accept both forms; the app's resolvePdsUrl already does.
Found by Greptile on #254.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements TECH-789. Every hypercerts-lexicon record certified.app writes (
app.certified.*,org.hypercerts.*,org.hyperboards.*) carries anapp.certified.signature.defs#createdViasignature, following the ATProtocol Attestation Spec.app.bsky.*andorg.impactindexer.*stay unsigned.What changed
Signing points:
groupServiceFetch, for every group service create and put, bound to the group DID;Signing fails open and never blocks a write.
Group activity route: it called the group service with a raw
fetch. It now goes throughgroupServiceFetch, so group activities are signed too./.well-known/did.json: serves the per-host did:web document. It returns 200 when a key is set, 404 when signing is off, and 500 when the key is malformed.Same-session reads:
getRecordandlistRecordsreturnlexToJsonoutput, so$bytessurvives the edit round trip. Legacy blobs keep their shape.Docs: env vars, AGENTS.md §16a and the public keys for both environments.
Tests:
scripts/verify-created-via.mjs, which verifies a stored record the way a third party would.Keys
did:web:staging.certified.appdid:key:zQ3shkw7ytTFdJmn3epcWaqTXED3tZFu8SbsCwZBfGAWFA868did:web:certified.appdid:key:zQ3shirLmopLSHcwUNnfBaoPMNCrtLWxLZtup2UpiX7oEV56PThe production
CREATED_VIA_SIGNING_KEYandCREATED_VIA_DIDare set in Vercel, scoped to Production only.Verified on staging
staging.certified.app/.well-known/did.jsonreturns 200 without authentication and serves the recorded key.scripts/verify-created-via.mjspasses for each record below: the CID matches the PDS and the signature verifies for the right repository.app.certified.actor.profile: saved in the appAfter merge
curl https://certified.app/.well-known/did.jsonshould return 200 withzQ3shirLm….Follow-up issue: server-side verification and a "Created via certified.app" badge.
🤖 Generated with Claude Code
The PR appears safe to merge, with a non-blocking compatibility fix needed in the verification script.
Fix with agent prompt
Summary
Adds
#createdViasignatures to personal writes, group writes, and the first-sign-in Certified profile seed.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Personal create or put] --> D[signCreatedVia] B[Group create or put] --> C[groupServiceFetch] C --> D E[First-sign-in profile seed] --> D D --> F{Signing succeeds?} F -->|Yes| G[Write with fresh createdVia] F -->|No| H[Write without createdVia] I[did.json] --> J[Publish public signing key]Reviews (1) · Last reviewed commit: "Merge main into staging (#259, groups sp..." · Reviewed by Greptile