-
v0.4.0
Stablereleased this
2026-05-26 01:18:12 +00:00 | 2 commits to main since this releaseBreaking changes
LoginFormdefault copy switched to "Atmosphere account" terminology (Issue #32) — Default English copy now reads "Sign in with your Atmosphere account" / "Your Atmosphere account handle — your PDS is detected automatically." instead of "Sign in with AT Proto" / "Your AT Protocol handle…". Apps that relied on the previous strings (e.g. UI tests asserting on button text, screenshot tests, translation overlays keyed on the old defaults) must update their expectations or pass explicitButtonText/HandleHintvalues to restore the old wording.LoginForm's string parameters also changed type fromstringtostring?to enableIStringLocalizer<LoginForm>resolution — source-compatible, no behaviour change for callers passing explicit valuesAtProtoClient.ApplyOAuthSessionAsyncsignature change — The method gained an optionalIAtProtoTokenStore? tokenStoreparameter inserted betweenoauthClientandcancellationToken. Source-compatible for callers using named arguments; binary-incompatible for positional callers — recompile required. Positional callers that previously passed(session, client, ct)must now pass(session, client, null, ct)or switch to named arguments. Required so factory-built clients can persist OAuth-refresh-rotated tokens back to the durable token storeAtProtoClientFactoryconstructor change — Constructor gained anIOAuthClientProvider? oauthClientProvider = nullparameter. Source-compatible for DI callers (Microsoft.Extensions.DependencyInjection auto-resolves the optional dependency); binary-incompatible for hand-rolled instantiation — recompile required. Without a registeredIOAuthClientProvider, factory-built per-request clients cannot refresh expired OAuth tokens
Added
-
"Atmosphere account" terminology & i18n in
LoginForm(Issue #32) — Default copy now uses the community-facing "Atmosphere account" umbrella term- New
HeadingTextandSubtitleTextparameters for an optional heading/subtitle rendered above the form (no default — rendered only when set) - Optional
IStringLocalizer<LoginForm>injection: when registered (e.g. viaservices.AddLocalization()with a.resxsource), default copy is resolved by parameter name (ButtonText,HandleLabel,HandleHint, etc.); explicit parameter values still take precedence - String parameters changed from
stringtostring?so consumers can opt in to localizer-resolved defaults
- New
-
Aspire hosting integration for PDS containers (Issue #31) — New
ATProtoNet.Aspire.Hostingpackage for adding the official Bluesky PDS container to .NET Aspire AppHostsAtProtoPdsContainerResource— Aspire container resource representing aghcr.io/bluesky-social/pdsinstance withIResourceWithConnectionStringsupportAddAtProtoPds()extension onIDistributedApplicationBuilder— Adds the PDS container with auto-generated secrets (admin password, JWT secret, PLC rotation key), dev mode enabled by default, and a persistent data volume- Fluent configuration:
WithHostname(),WithPlcUrl(),WithAppView(),WithCrawlers(),WithProductionMode(),WithBlobUploadLimit(),WithReportService(),WithEmail() - Configurable port mapping and image tag selection
- Replaces the need for manual Docker/Podman PDS setup during development
-
PDS hosting package (Issue #2) — New
ATProtoNet.Pdspackage for building AT Protocol Personal Data ServersPdsService— core business logic for account management, session handling, record CRUD, and blob operationsPdsSessionService— JWT token issuing and validation with HMAC-SHA256 signingIAccountStore/InMemoryAccountStore— pluggable account persistence with DID, handle, email, and signing key managementIRepoStore/InMemoryRepoStore— pluggable repository storage for records and blobs with cursor-based paginationPdsHostingExtensions—AddAtProtoPds()DI registration andMapAtProtoPds()XRPC endpoint mapping- Full XRPC endpoint support:
com.atproto.server.createAccount,createSession,getSession,refreshSession,describeServer - Repository endpoints:
com.atproto.repo.createRecord,getRecord,putRecord,deleteRecord,listRecords - Blob endpoints:
com.atproto.repo.uploadBlob,com.atproto.sync.getBlob - Custom store implementations via
AddAtProtoPds<TAccountStore, TRepoStore>() - PBKDF2 password hashing with 100k iterations, SHA-256 based CID computation
- Bearer token authentication with authorization checks for repo ownership
-
Native Standard.site integration (Issue #9) — First-class support for Standard.site long-form publishing lexicons
PublicationRecordmodel forsite.standard.publication— blog/site identity with URL, name, description, icon, theme, and preferencesDocumentRecordmodel forsite.standard.document— published documents with title, path, tags, content union, cover image, and Bluesky post referenceSubscriptionRecordmodel forsite.standard.graph.subscription— follow/subscribe to publicationsBasicTheme,ThemeColorRgb,ThemeColorRgbamodels forsite.standard.theme.basicandsite.standard.theme.colorStandardSiteClientwith full CRUD for publications, documents, and subscriptions via AT Protocol repo operations- Exposed as
AtProtoClient.Siteproperty, following the same pattern asBsky,Chat, andOzone
-
Lexicon migrations and publishing (Issue #14) — Schema migration pipeline and publishing workflow for the
atproto-lexgenCLI toolILexiconMigrationinterface andDelegateMigrationfor record transforms between schema revisionsMigrationBuilderfluent API for composing migrations:AddProperty,RemoveProperty,RenameProperty,ApplyLexiconMigrationRunner— builds and executes ordered migration chains, validates continuity, scaffolds migrations fromDiffResultLexiconPublisher— publishes schemas to directories with baseline diff validation, auto-revision bumping, and breaking change detectionatproto-lexgen migrateCLI command — scaffold migrations from schema diffs or apply migration files to JSON recordsatproto-lexgen publishCLI command — publish schemas with version tracking,--forcefor breaking changes,--no-bumpoption- JSON migration file format with
addProperty,removeProperty,renamePropertyoperations
-
Ozone moderation client (Issue #18) — Full
tools.ozone.*namespace support viaclient.OzoneOzoneClienttop-level client aggregating all Ozone sub-clientsModerationClient— emitEvent, getEvent, getRecord, getRepo, queryEvents, querySubjects, searchReposCommunicationClient— createTemplate, deleteTemplate, listTemplates, updateTemplateTeamClient— addMember, deleteMember, listMembers, updateMemberSetClient— upsertSet, deleteSet, addValues, deleteValues, getValues, querySetsOzoneServerClient— getConfigSignatureClient— findCorrelation, searchAccounts, findRelatedAccounts- Polymorphic moderation event types (takedown, label, comment, mute, email, tag, etc.)
SubjectReviewStateandTeamMemberRoleconstants
-
Aspire integration package (Issue #5) —
ATProtoNet.Aspirepackage for .NET Aspire service defaultsAddAtProtoClient()extension onIHostApplicationBuilder— registersAtProtoClientas a singleton with configuration binding,IHttpClientFactory, and optional standard resilienceAtProtoClientSettingsforIConfigurationbinding (InstanceUrl, RelayUrl, AutoRefreshSession, DisableHealthChecks, DisableResilience)AtProtoPdsHealthCheck— health check verifying PDS connectivity viacom.atproto.server.describeServer- Standard HTTP resilience (retry, circuit breaker) via
Microsoft.Extensions.Http.Resilience
-
Lexicon plugin support for custom types (Issue #6) — Runtime registration of custom record types and union variants via NuGet packages
ILexiconPlugininterface for plugins to register custom types at startupILexiconTypeRegistrarfor registering record types and union variants[LexiconPlugin]assembly attribute for auto-discoveryLexiconTypeRegistry— Singleton registry withLoadPlugin<T>(),LoadPluginsFromAssembly(), andCreateOptions()for plugin-aware JSON serialization- Runtime union variant registration augments built-in
[JsonDerivedType]attributes viaJsonTypeInfomodifier
-
Missing sync endpoints & Sync v1.1 support (Issue #21) — Complete com.atproto.sync coverage and Sync v1.1 fields
SyncClient.GetRepoStatusAsync— Get repository hosting statusSyncClient.ListHostsAsync— Enumerate upstream hosts consumed by a relaySyncClient.GetHostStatusAsync— Get status of a specified upstream hostSyncClient.ListReposByCollectionAsync— Enumerate DIDs with records in a given collectionAccountHostingStatusconstants: takendown, suspended, deleted, deactivated, desynchronized, throttledHostStatusconstants: active, idle, offline, throttled, bannedSyncEvent(#sync) firehose message type for Sync v1.1 repo state recoveryCommitEvent.PrevDataandCommitEvent.BlobsSync v1.1 fieldsRepoOp.Prevfield for inductive firehose verification
-
Auto-register XRPC endpoints with DI (Issue #15) — Server-side XRPC endpoint handler infrastructure
IXrpcEndpoint— Base interface for XRPC endpoint handlers with NSID identificationIXrpcQuery<TParams, TOutput>/IXrpcQuery<TOutput>— Interfaces for XRPC query endpoints (GET)IXrpcProcedure<TInput, TOutput>/IXrpcProcedureVoid<TInput>— Interfaces for XRPC procedure endpoints (POST)[XrpcEndpoint]attribute — Assembly scanning marker with optional NSID overrideAddXrpcEndpoint<T>()— Register a single XRPC endpoint handler in DIAddXrpcEndpointsFromAssembly()— Assembly scanning for[XrpcEndpoint]-attributed handlersMapXrpcEndpoints()— Maps all registered handlers as ASP.NET Core minimal API routes at/xrpc/{nsid}- Query parameter binding, JSON body deserialization, and XRPC error format support
-
Firehose event parsing, verification, and typed consumer (Issue #27) — Full firehose commit verification pipeline and advanced consumer
FirehoseEventParser— Decodes raw CBOR firehose frames into typedFirehoseMessageobjects with DAG-CBOR JSON normalizationFirehoseVerifier— CID integrity verification for commit and sync events, commit signature verification against DID document signing keysVerificationResult— Structured verification result with error detailsTypedFirehoseConsumer— High-level consumer with CBOR parsing, collection filtering, CID/signature verification, and periodic cursor persistenceTypedFirehoseConsumerOptions— Configuration for verification, collection filters, cursor persistence interval, and reconnectionIFirehoseCursorStore— Interface for persistent cursor storage enabling resumable firehose consumptionInMemoryFirehoseCursorStore— In-memory cursor store for development and testing
-
Merkle Search Tree (MST) implementation (Issue #19) — Full in-memory MST for AT Protocol repository data structure
MstKeyDepth— Computes key depth via SHA-256 leading-zero counting (fanout 4), matching AT Protocol specMstNodeData— CBOR-serializable MST node with deterministic DAG-CBOR encoding/decoding via tag 42 CID linksMerkleSearchTree— Complete MST withAdd,Update,Delete,Get,GetEntries,Serialize/Deserialize,ComputeRootCid, andValidateoperations- Layer-based top-down construction, DoS protection limits (max 256 entries/node, max 64 depth)
-
chat.bsky DM support (Issue #17) — Full Bluesky direct messaging client
ConvoClient— 17 endpoints:ListConvos,GetConvo,GetConvoForMembers,GetConvoAvailability,GetMessages,SendMessage,SendMessageBatch,DeleteMessageForSelf,LeaveConvo,MuteConvo,UnmuteConvo,UpdateRead,UpdateAllRead,AcceptConvo,AddReaction,RemoveReaction,GetLogChatActorClient—DeleteAccount,ExportAccountDataChatClientsgrouping accessible viaAtProtoClient.Chat- All chat requests automatically proxied via per-request
atproto-proxyheader (requirestransition:chat.bskyOAuth scope) - Per-request proxy override support in
XrpcClient— chat proxy doesn't affect other XRPC calls - Complete model types:
ConvoView,MessageView,DeletedMessageView,ChatMemberView,MessageInput, reaction models, all request/response types ChatDeclarationRecordandChatAllowIncomingconstants (all/none/following)
-
Labeler service support (Issue #25) — Labeler service information, label definitions, and header management
LabelerClient.GetServicesAsync— Fetch labeler service info and label value definitionsLabelerServiceRecord— Record type for declaring labeler services with policiesLabelValueDefinition— Custom label definitions with severity, blur behavior, default settings, and localized stringsLabelerViewDetailed,LabelerView,LabelerViewerState— View types for labeler servicesSetLabelers()/ClearLabelers()onXrpcClientandAtProtoClient— Automaticatproto-accept-labelersheader injectionStandardLabelValuesconstants: porn, sexual, nudity, graphic-media, gore, spam, impersonation, etc.LabelSeverity,LabelBlurs,LabelDefaultSettingconstant classesLabelerClientwired intoBlueskyClients.Labeler
-
atproto-proxy header support (Issue #24) — Route XRPC requests through AT Protocol service proxies
ServiceProxystatic helper withBuild()method and well-known constants (BskyAppView,BskyChat,AtProtoLabeler,AtProtoPds)- Pre-built header values:
BskyAppViewHeader,BskyChatHeader,BskyAppViewDid,BskyChatDid SetProxy()/ClearProxy()methods on bothXrpcClientandAtProtoClient
-
did:web resolver & unified DID resolution (Issue #28) — Resolve
did:webidentifiers and dispatch to correct resolverDidWebResolver— Fetcheshttps://<domain>/.well-known/did.json, validates document ID matches, SSRF prevention (IP address blocking), HTTPS enforcement, localhost exception for developmentDidResolver— Unified dispatcher:did:plc→PlcClient,did:web→DidWebResolverDidWebExceptionwith typedDidWebErrorKind(InvalidDid, NotFound, HttpError, NetworkError, ParseError, ValidationError)
-
Missing Bluesky graph features (Issue #26) — Starter packs, relationships, thread muting, and postgate support
- Records:
StarterPackRecord,StarterPackFeedItem,PostgateRecord - Views:
StarterPackView,StarterPackViewBasic - Relationships:
Relationship,NotFoundActor,GetRelationshipsResponse,GetKnownFollowersResponse - Starter pack responses:
GetStarterPackResponse,GetStarterPacksResponse,GetActorStarterPacksResponse,SearchStarterPacksResponse GraphClientmethods:GetRelationshipsAsync,GetKnownFollowersAsync,MuteThreadAsync,UnmuteThreadAsync,GetStarterPackAsync,GetStarterPacksAsync,GetActorStarterPacksAsync,SearchStarterPacksAsync
- Records:
-
Video upload & processing client (Issue #22) —
app.bsky.video.*XRPC endpointsVideoClientwithUploadVideoAsync,GetJobStatusAsync,GetUploadLimitsAsyncVideoModels:JobStatus,JobStateconstants,GetJobStatusResponse,UploadVideoResponse,GetUploadLimitsResponseVideoClientwired intoBlueskyClients.Video
-
Well-known Bluesky permission set NSIDs (Issue #29) —
AtProtoScopes.PermissionSetsconstants- Constants for all
app.bsky.auth*permission sets:FullApp,ManageProfile,CreatePosts,DeletePosts,ManagePosts,ManageFollows,ManageListsAndPacks,ViewNotifications,ManageNotifications,ManageFeedDeclarations,ManageLabelerService,ManagePreferences,ManageModeration,ViewAll
- Constants for all
-
Atproto-Repo-Rev header tracking (Issue #30) — Automatic extraction and exposure of repository revision headers
LatestRepoRevproperty onXrpcClientandAtProtoClient- Extracted from all XRPC responses via the
Atproto-Repo-Revheader
-
HTTP rate limiting with automatic retry (Issue #23) — Built-in 429 handling with configurable retry behavior
RateLimitInfomodel with Limit, Remaining, Reset, and IsExceeded properties- Automatic retry on HTTP 429 with
Retry-After/RateLimit-Resetheader support and exponential backoff fallback LatestRateLimitInfoproperty onXrpcClientandAtProtoClient- Configurable
MaxRateLimitRetries(default: 3, set to 0 to disable)
-
DAG-CBOR encoding/decoding layer (Issue #20) — DRISL-CBOR implementation for AT Protocol data model
DagCborEncoder— Deterministic CBOR encoding with sorted map keys,$link→ CID tag 42,$bytes→ byte string, float rejectionDagCborDecoder— CBOR decoding with CID tag 42 →$link, byte string →$bytes, validation of sorted keys and no-float constraintsCidComputation— CIDv1 computation with SHA-256, DAG-CBOR (0x71) and raw (0x55) codecs, Base32Lower encoding/decoding, CID verification
-
OAuth scope constants & granular permission builders (
AtProtoScopes) — Full AT Protocol Permissions spec support- Transitional scope constants:
AtProto,TransitionGeneric,TransitionChatBsky,TransitionEmail - Convenience presets:
Default,WithChat,AuthOnly Repo()— Record collection permissions withRepoActionflags (Create, Update, Delete), single or multiple collections, wildcard supportRpc()— Service authentication (RPC) permissions with Lexicon method and audience parameters, DID fragment encodingBlob()— Blob upload permissions with MIME type patterns (*/*,video/*, etc.)Account()— Account attribute permissions (email, repo, status) with Read/Manage actionsIdentity()— Identity attribute permissions (handle, wildcard) with Manage/Submit actionsInclude()— Permission set references for published Lexicon-based permission bundles with optional audience inheritanceCombine()— Merge and deduplicate multiple scope strings- Replaced hardcoded scope strings in
OAuthModelsandAtProtoOAuthServerOptionswithAtProtoScopes.Default
- Transitional scope constants:
-
Custom relay URL configuration (Issue #8) — Configurable relay WebSocket URL for firehose
WithRelayUrl()onAtProtoClientBuilder(default:wss://bsky.network)RelayUrlproperty onAtProtoClientOptionsCreateFirehoseClient()andCreateFirehoseConsumer()convenience methods onAtProtoClient
-
EF Core token store (
ATProtoNet.Server.EntityFrameworkCore) — New package for database-backed token storage (Issue #3)EfCoreAtProtoTokenStore<TContext>— GenericIAtProtoTokenStoreimplementation usingIDbContextFactory<TContext>- ASP.NET Core Data Protection encryption for stored tokens
AtProtoTokenEntitywith DID primary keyAtProtoTokenDbContextwithConfigureAtProtoTokenModel()for use in custom DbContextsAddAtProtoEfCoreTokenStore<TContext>()DI extension
-
Security hardening — Comprehensive SSRF prevention, TLS enforcement, and input validation
- Accurate private IP range detection using
IPAddress.TryParsecovering RFC 1918, CGN (100.64/10), loopback, link-local, and IPv6 private ranges - IPv6 bracket host blocking in DID:web resolution (all bracketed IPs rejected — use domain names)
- TLS enforcement in
XrpcClient.SetBaseUrl()— HTTP only allowed for localhost/loopback - Exact token matching for
atprotoscope validation (prevents substring false-positives) - Open redirect prevention in OAuth callback return URLs
- Error message sanitization (truncation to 200 chars) to prevent leaking internal details
- DPoP key disposal on all OAuth error paths (prevents cryptographic key leaks)
- Concurrent session refresh guard via
SemaphoreSliminAtProtoClient - Restrictive Unix file permissions (700) on
FileAtProtoTokenStoredirectory - 54 new security-focused tests (362 total)
- Accurate private IP range detection using
-
Aspire auto-detection — Automatic HTTP loopback URL discovery for AT Proto OAuth
TryGetLoopbackHttpUrl()inspectsIServerAddressesFeaturefor HTTP bindings when request arrives on HTTPS- Normalizes
localhost→127.0.0.1for AT Proto loopback compatibility - Zero-config: works automatically with Aspire, Kestrel multi-bind, and reverse proxy setups
-
Transparent cross-origin cookie relay — Automatic auth cookie relay for localhost/127.0.0.1 mismatch
- AT Proto loopback OAuth requires
http://127.0.0.1for the callback, but the user's browser may be onhttps://localhost(e.g., in Aspire). The auth cookie set on127.0.0.1is invisible onlocalhost. - The SDK now detects when the callback origin differs from the login origin, generates a one-time relay code (128-bit, 2-minute expiry), and redirects to
{loginOrigin}/atproto/relay?code=xxxto issue the cookie on the correct domain. - Return URL is stored server-side (keyed by OAuth state) instead of only in a cookie, fixing the cross-domain cookie loss.
- Zero-config: No
BaseUrl,OnSigningInhooks, or relay middleware needed. JustAddAtProtoAuthentication()+MapAtProtoOAuth(). - 22 new cookie relay tests (384 total)
- AT Proto loopback OAuth requires
-
Lexicon code generator — Bidirectional
dotnet tool(atproto-lexgen) for AT Protocol Lexicon schemasatproto-lexgen csharp— Generate C# classes from Lexicon JSON schema files (records, objects, enums, tokens)atproto-lexgen lexicon— Generate Lexicon JSON schemas from compiled .NET assemblies via reflectionatproto-lexgen diff— Compare baseline and current Lexicon schemas, detect breaking changes per AT Protocol evolution rules- Matches existing SDK patterns:
sealed class,required/initproperties,[JsonPropertyName],$typeexpression-body - Supports all Lexicon types: record, object, string enum, token, ref, union, array, blob
- Schema evolution validation: detects added/removed properties, type changes, required status changes, constraint tightening
--strictmode exits with code 1 on breaking changes (for CI integration)- Automatic revision bump suggestions for non-breaking changes
-
Cryptography utilities (
AtProtoCrypto,AtProtoKey) — AT Protocol cryptographic operations- P-256 (NIST secp256r1) and K-256 (secp256k1) key pair generation
- ECDSA signing and verification with SHA-256 and low-S normalization
- Compressed public key export/import with EC point decompression (modular arithmetic)
- Multikey encoding/decoding (base58btc with multicodec prefix)
did:keygeneration and parsing (round-trips through multikey)- PKCS#8 private key export/import
- Base58 Bitcoin encoding/decoding
-
CAR file reader (
CarReader) — Parse Content Addressable aRchive (CAR v1) files- Used for consuming
com.atproto.sync.getReporesponses - CID parsing (CIDv0 and CIDv1), DAG-CBOR header decoding
- Block lookup by CID, root block access
- Stream and byte array input support
- Used for consuming
-
PLC directory client (
PlcClient) — Interact with PLC directory servers- DID document resolution (
ResolveDidAsync) with 404/410 error handling - Operation log, audit log, and latest operation retrieval
- Current PLC data access
- Health check endpoint
- Full DID document model:
DidDocument,VerificationMethod,ServiceEndpoint - PLC operation model:
PlcOperation,PlcAuditEntry,PlcData - Convenience methods:
GetHandle(),GetPdsEndpoint()onDidDocument
- DID document resolution (
-
Service auth JWT generation (
ServiceAuthGenerator) — Inter-service authentication- JWT generation with
iss(service DID),aud(target),exp,iat,jti,lxmclaims - ES256 (P-256) and ES256K (K-256) signing via
AtProtoKey - 60-second default expiry, 5-minute maximum enforcement
- Used for Feed Generators, Labelers, and relay services
- JWT generation with
-
Lexicon code generator packaging —
atproto-lexgenis now a publishabledotnet tool- NuGet package metadata:
PackageId,Version,Authors,PackageTags,License,RepositoryUrl - Install globally via
dotnet tool install -g ATProtoNet.LexiconGenerator
- NuGet package metadata:
-
Documentation — Comprehensive documentation for all new features
- New guides: PDS Hosting, Chat & DMs, Ozone Moderation, Standard.site, .NET Aspire, Video Upload, Labeler Services, Cryptography, DID Resolution, Lexicon Code Generator, XRPC Endpoint Handlers
- Updated guides: Firehose Streaming (TypedFirehoseConsumer, verification, cursor persistence), Getting Started (new packages, builder options), Server Integration (EF Core token store), API Reference (all new client types)
Fixed
-
OAuth, firehose, and repo correctness pass (F1–F15 + G1–G14 + review follow-up) — Series of fixes addressing review findings across the OAuth, firehose, and repo subsystems
- Commit signature verification (
FirehoseVerifier) — UseCborConformanceMode.Strictinstead ofCtap2Canonical. The previous mode forbade all CBOR tags, but DAG-CBOR requires tag 42 for CIDs, so every real commit threwCborContentExceptionand verification failed for the wrong reason. Canonical-form integrity is preserved by the byte-for-byte splice of the original buffer - MST canonical form (
MerkleSearchTree) — Restored empty parent-layer wrapping inSplitAndInsertand added matching empty-parent wrapping inBuildLayerTopDownso incrementalAddand bulkCreateFromEntriesproduce the same root CID as atproto/ts.Create(entries)now delegates toCreateFromEntriesso both public factories use the spec-conformant builder - Firehose at-least-once semantics (
FirehoseConsumer) — Reconnect cursor only advances when the consumer callsAcknowledge(seq). WhenAcknowledgeis never called, the cursor falls back to the current frame's seq (at-most-once); the docstring spells out the contract explicitly. The monotonic floor is now pre-seeded with the caller's resume cursor so a hostile first frame can't rewind below the intended resume point - CAR block CID codec policy (
CarReader+FirehoseVerifier) —VerifyAllBlockCidsnow throws onUnknownCodecin addition toMismatch. The staticFirehoseVerifier.VerifyCarBlockCidspath also fails closed onUnknownCodec, so the cheap pre-check and the full signature path apply the same policy - OAuth refresh persistence (
AtProtoClient) — Rotated tokens are written toIAtProtoTokenStoreBEFORE the in-memory session is mutated. A store-write failure now surfaces immediately rather than silently desyncing memory and disk (the old failure mode left the persisted store with the dead refresh token, logging users out on next process restart) - OAuth refresh token store wired —
AtProtoClient.ApplyOAuthSessionAsyncgained an optionalIAtProtoTokenStore? tokenStoreparameter thatAtProtoClientFactorypasses through, so refresh-rotated tokens land in durable storage instead of only the per-requestInMemorySessionStore - Refresh-lock around
ApplyOAuthSessionAsync— The session swap now holds_refreshLock, preventing a timer-driven refresh from racing the swap and corrupting state - Bounded timer-driven refresh —
OnRefreshTimerElapseduses a 30-secondCancellationTokenSourceso a slow token endpoint can't pin_refreshLockindefinitely and block foregroundLogoutAsync/ApplyOAuthSessionAsync Disposerace with timer callback — SyncDispose()drains in-flight callbacks viaTimer.Dispose(WaitHandle); the callback'sReleaseis wrapped intry/catch ObjectDisposedExceptionso a late-firing release on a disposed semaphore can no longer escapeasync voidand crash the process._oauthSessionand_refreshLockare now disposed inDisposeandDisposeAsyncLogoutAsyncclears_oauthTokenStore— Defensive cleanup so a subsequent re-login with a differenttokenStorearg doesn't inherit a stale referenceOAuthClientconstructed lazily onIOAuthClientProvider.TryGetClient— Only when explicitClientMetadatais configured (the production case). Loopback callers must still driveStartLoginAsyncto materialize a client, since the loopbackclient_idencodes the live request's callback URL- JWT pre-validator algorithm allowlist (
AtProtoAuthenticationHandler) — Now allowlistsES256/ES256K/ES384/ES512/EdDSA/RS256/RS384/RS512/PS256/PS384/PS512only. Previously only rejectedalg=none, so symmetric HS256 forgeries reached the PDS unchallenged - Handle resolution requires HTTPS + DNS agreement (
AuthorizationServerDiscovery) —ResolveHandleAuthoritativeAsyncnow runs HTTPS well-known and DNS-over-HTTPS lookups concurrently and fails closed when they return different DIDs. (Note: both transports currently share the same TLS trust root viadns.google— true authority diversification needs a system DNS path) did:webid comparison is case-insensitive for host (AuthorizationServerDiscovery) — DNS host names are case-insensitive per RFC 1035; the prior strictOrdinalcompare rejected validdid:web:Example.comdocuments.did:plcremains strictly case-sensitiveAtProtoTokenData/OAuthSessionResultgainedIsHandleVerified— Persisted and restored across factory hydration. Default-claims now emit"handle.invalid"asClaimTypes.Namewhen the handle isn't bidirectionally verified, with an explicithandle_verifiedclaim alongside the actualdidandhandle. Behavior change for existing OAuth sessions: tokens persisted before this release deserialize withIsHandleVerified=false, soUser.Identity.Nameshows"handle.invalid"until users re-loginTryReadSeqpropagatesOperationCanceledException— Previously swallowed by an unfiltered catch, breaking cancellation propagation through the cursor-advance logicWriteMapHeaderrejects oversized counts — ThrowsArgumentOutOfRangeExceptionon negative counts and now emits the 4-byte (CBOR 0x1a) header for counts ≥ 65536. Previously silently truncated to 16 bits, producing malformed CBORAtProtoClient.Dispose/DisposeAsyncreleases_oauthSessionand_refreshLock— DPoP ECDsa key and SemaphoreSlim wait handles no longer leak to GC finalization
- Commit signature verification (
-
Packaging & release pipeline — Release artifact hygiene
Aspire.Hostingdependency inATProtoNet.Aspire.Hostingupgraded from9.2.1to9.5.2, picking upKubernetesClient 17.0.14and resolving the transitive moderate-severity NU1902 advisory (GHSA-w7r3-mgwf-4mqq)Microsoft.EntityFrameworkCore.Relationaldependency inATProtoNet.Server.EntityFrameworkCoreupgraded from10.0.0-preview.4.25258.110to stable10.0.0(resolves NU5104 "stable release should not have a prerelease dependency")FirehoseConsumerSamplemarkedIsPackable=falseso it no longer leaks intodotnet packoutput- Removed duplicate
README.md<None Include>items fromATProtoNet,ATProtoNet.Server, andATProtoNet.Blazorcsprojs —Directory.Build.propsalready packs the root README into every package (resolves NU5118) - Removed stale hardcoded
<Version>0.3.0</Version>and duplicated package metadata fromATProtoNet.LexiconGenerator.csprojso it inherits the shared version fromDirectory.Build.props - Removed the
packagejob from.forgejo/workflows/ci.yml; publishing is now driven exclusively by thereleaseworkflow (triggered byv*tags or manualworkflow_dispatch), so version bumps onmainno longer publish to the Forgejo NuGet feed before a release tag is cut
-
Issue templates — Converted from invalid hybrid format (YAML frontmatter + Markdown body in
.ymlfiles) to proper Forgejo YAML form templates with structuredbody:sections -
Cryptographic security hardening — Fixes from security audit of crypto primitives
- Low-S normalization —
NormalizeLowSwas a complete no-op (dead code). Now compares S against the actual curve half-order and computesorder - Swhen needed. Prevents signature malleability. - High-S signature rejection —
Verify()now rejects signatures with S > half-order, enforcing AT Protocol's low-S requirement ImportPrivateKeycurve validation — Validates the imported key's curve OID matches the declaredKeyCurveparameter. Prevents silent identity corruption from curve mismatch.DecompressPointrange check — Validates X coordinate is in range[0, p)before modular arithmetic- JWT
audiencevalidation —ServiceAuthGenerator.CreateTokennow rejects null/whitespace audience - Base58 performance — Replaced LINQ
.Any()with aforloop in hot path - 4 new crypto security tests (455 total)
- Low-S normalization —
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
1 download