Skip to content

Release: AWS secrets adapters, crypto VFS vault, docs overhaul, and audit hardening#13

Merged
tgiachi merged 85 commits into
mainfrom
develop
Jun 29, 2026
Merged

Release: AWS secrets adapters, crypto VFS vault, docs overhaul, and audit hardening#13
tgiachi merged 85 commits into
mainfrom
develop

Conversation

@tgiachi

@tgiachi tgiachi commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Summary

Release of everything accumulated on develop since the last main cut. Three themes: new modules, a full documentation overhaul, and a multi-batch robustness/security audit remediation.

New features

  • SquidStd.Secrets.Aws — new module: KMS envelope-encryption secret protector, AWS Secrets Manager store, and RegisterKmsSecretProtector / RegisterAwsSecretsManagerStore DI extensions. Adds ISecretStore.ListNamesAsync (+ FileSecretStore).
  • Crypto VFS vault — encrypted, lockable virtual filesystem (Argon2id + per-entry chunked AES-GCM + encrypted index) over any IVirtualFileSystem; on-disk vault now persists correctly.
  • Persistence durabilityPersistenceConfig.DurabilityMode (Buffered default / Durable): Durable fsyncs journal appends and snapshot temp files before the atomic rename.

Documentation

  • Diátaxis-structured DocFX site: tutorials (crypto, vfs, secrets, actors, persistence, command-dispatcher), concept pages with Mermaid diagrams, how-to / provider-decision guides, reference articles.
  • Rewritten landing page and getting-started; root README grouped by domain with TOC, templates, fuller example.
  • All 41 package READMEs normalised to a consistent template; compiling sample projects with #region step markers.

Audit remediation (robustness, durability, security)

  • Persistence: write-ahead ordering (journal before in-memory apply); per-type journal replay watermark (fixes partial-snapshot data loss); serialized snapshots; full-envelope checksum (V2, V1 still loads); short-frame startup guard; TypeId-in-filename with legacy auto-migration.
  • Crypto/VFS: bounded unauthenticated length prefixes (EntryCipher, VaultBlob); thread-safe vault index; in-memory FS returns copies; physical FS root-containment; ZIP vault persistence + ListAsync crash fix.
  • Actors: dispose now drains queued messages within a configurable ShutdownDrainTimeout instead of dropping them; race-safe dispose guard.
  • Network: configurable TCP MaxFrameLength (1 MiB) to bound per-connection buffering; UDP listener/route cleanup on stop; session marked closed on idle sweep.
  • RabbitMQ: topology marked declared only after a successful declare (retries on failure).

Test plan

  • dotnet build SquidStd.slnx -c Debug — 0 errors
  • Full suite: 870 passing, 0 failures
  • Backward compatibility preserved: legacy V1 snapshots load; legacy snapshot filenames auto-migrate on first load; DurabilityMode defaults to Buffered

tgiachi added 30 commits June 28, 2026 14:20
Promote VfsPath to SquidStd.Vfs.Abstractions (public) so the crypto
decorator and the VFS providers share identical path normalization.
- KmsSecretProtector implements ISecretProtector via AWS KMS data keys
- KmsEnvelope frames [wrappedKeyLen|wrappedKey|nonce|tag|ciphertext] with AES-GCM
- zero the plaintext data key after sealing/opening; dispose owns KMS client
- LocalStack-backed integration test round-trips a >4KB payload
- AwsSecretsManagerStore implements ISecretStore over AWS Secrets Manager
- Set upserts via PutSecretValue, falling back to CreateSecret when absent
- Delete gates on existence so a missing secret returns false on both real AWS and LocalStack
- ListNamesAsync paginates ListSecrets and strips the configured NamePrefix
- store disposes its Secrets Manager client
- LocalStack-backed integration test covers the full set/get/exists/list/delete cycle
tgiachi added 28 commits June 28, 2026 22:19
Demonstrate IPgpKeyring.SaveAsync to the file-backed store and a fresh
PgpKeyring.LoadAsync round-trip, instead of registering a key store that
was never used. Clarify the PrivateArmored null-forgiving operator.
Drive CryptoFileSystem over an in-memory backend through the full
unlock/write/lock/reopen lifecycle so the encrypted round-trip is real,
and document that the RegisterCryptoVault disk backend (ZipFileSystem)
cannot currently be locked due to a ZipArchive Update-mode limitation.
Professional documentation overhaul (Diátaxis on DocFX):
- rewrite landing + getting-started
- add reference articles + normalise all 41 package READMEs to a uniform template
- add 5 Concept pages with Mermaid diagrams, 4 how-to + 4 provider decision guides
- add 4 compiling sample projects (Crypto/Vfs/Secrets/Actors) and 6 tutorials
- full link/diagram audit; every package has README + article + TOC entry

# Conflicts:
#	src/SquidStd.Actors/README.md
#	src/SquidStd.Generators/README.md
#	src/SquidStd.Messaging.Sqs/README.md
- ZipFileSystem.ListAsync no longer reads ZipArchiveEntry.Length (unavailable in
  ZipArchiveMode.Update for entries written this session); track uncompressed sizes
  in a dictionary on write/delete and fall back safely so listing never throws
- CryptoFileSystem.Dispose now disposes its inner filesystem after Lock(), so a
  ZipFileSystem backend flushes its archive to disk and the vault persists
- this fixes RegisterCryptoVault throwing InvalidOperationException on Lock()/Dispose()
  via PruneOrphans(), which made on-disk encrypted vaults unusable
- add ZipFileSystem.ListAsync same-session test and disk-backed CryptoFileSystem
  lock + cross-instance persistence tests (previously untested: tests used InMemoryFileSystem)
- update the Vfs sample and tutorial to demonstrate real on-disk vault persistence
Fix on-disk crypto vault: ZipFileSystem.ListAsync no longer crashes on
ZipArchiveEntry.Length in update mode, and CryptoFileSystem.Dispose flushes
its inner filesystem so RegisterCryptoVault persists to disk.
…e actor dispose race-safe

- crypto: bound the unauthenticated per-record length prefix in EntryCipher.DecryptAsync
  against the configured chunk size before allocating, so a corrupt or tampered vault
  cannot trigger OOM or a negative-size crash
- crypto: reject VaultBlob payloads shorter than nonce+tag instead of producing an opaque
  negative-length slice on a truncated index
- crypto: make VaultIndex thread-safe (lock around Set/TryGet/Remove/Serialize, Entries
  returns a point-in-time snapshot) so concurrent vault operations cannot corrupt the map
- vfs: InMemoryFileSystem.ReadAllBytesAsync returns a copy so callers mutating what they
  read no longer corrupt the stored bytes
- vfs: PhysicalFileSystem.Resolve validates the resolved path stays within the root,
  rejecting segments that escape it (e.g. drive-rooted segments on Windows)
- vfs: delete the dead duplicate SquidStd.Vfs/Internal/VfsPath.cs (unreferenced)
- actors: claim the dispose guard atomically (Interlocked) and read it via Volatile so
  concurrent double-dispose cannot both run teardown
…P, retry failed declares

- persistence: replay each journal entry against its own type's snapshot watermark instead of a
  single global maximum, so a partial snapshot save (one bucket persisted, another not) no longer
  silently drops the lagging type's journaled writes on the next startup
- persistence: discard journal records whose framed length is smaller than the fixed entry header
  as a truncated tail, instead of letting the decoder crash startup
- network: clear the UDP server's endpoint→listener routes on Stop so a later Start cannot send
  through a stale, disposed socket
- network: mark a UDP session's connection closed when it is removed by the idle sweep, so holders
  observe IsConnected == false (CloseAsync stays idempotent for the explicit-close path)
- rabbitmq: only keep a queue/exchange marked as declared when the declare succeeds; undo the
  optimistic mark on failure so a later call retries instead of assuming the topology exists
Previously DisposeAsync cancelled the shutdown token before completing the mailbox. Because the
mailbox was bound to that token, cancelling first aborted the block and silently discarded any queued
fire-and-forget messages.

- decouple the mailbox from the shutdown token so completing it drains the queue rather than aborting
- DisposeAsync now completes the mailbox and drains queued work (Tells run, Asks reply) within the new
  ActorOptions.ShutdownDrainTimeout (default 5s); on timeout it cancels in-flight handlers, grants a
  brief grace period, then faults any still-pending requests with ObjectDisposedException
- add ActorOptions.ShutdownDrainTimeout
- document the new shutdown contract in the Actors README
…n in memory

Upsert/Remove now build the entry and append it durably before mutating the in-memory bucket and
committing LastSequenceId. A failed append leaves memory and the sequence counter untouched, so they
never diverge from what journal replay would rebuild.
…ot interleave

Wrap the whole snapshot operation (capture, save, delete-empty, trim) in a dedicated lock so autosave,
an explicit call, and StopAsync can no longer interleave their save/trim phases and regress a snapshot
below an already-trimmed journal sequence.
…payload

New snapshots are Version 2 with a checksum covering Version, LastSequenceId, TypeId, TypeName,
SchemaVersion and Payload, so a corrupted sequence field is now detected. Version 1 files keep loading
under the legacy payload-only checksum. Adds a two-span FNV overload to ChecksumUtils.
… writes

In DurabilityMode.Durable the journal opens its append stream WriteThrough and flushes to disk after each
write/batch, and the snapshot fsyncs its temp file before the atomic rename. Buffered (default) keeps the
current OS-cache flush. Documents the option and its portability limit.
…egacy names

Snapshot files are now named snake(typeName)_{typeId}{suffix}, so two type names that snake-case to the
same string no longer collide. LoadBucketAsync renames a legacy (pre-TypeId) file to the new scheme on
first access; DeleteBucketAsync removes both forms. ISnapshotService Load/Delete gain a typeId parameter.
…d buffering

Add a configurable maxFrameLength (default 1 MiB), propagated from SquidTcpServer to each accepted
client. When the pending buffer exceeds the cap without completing a frame, or a framer reports an
oversized frame, the connection is closed before the buffer grows further.

@github-advanced-security github-advanced-security AI 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.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@tgiachi
tgiachi merged commit c435a67 into main Jun 29, 2026
3 checks passed
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.

2 participants