Commit Graph

803 Commits

Author SHA1 Message Date
CarterPerez-dev ecaacc48fa chore(monitor): replace Makefile with Justfile (project convention) 2026-05-01 14:53:28 -04:00
CarterPerez-dev a67ef20680 feat(monitor/main): wire bus + hub + snapshot + heartbeat under errgroup; expose /v1/snapshot, /v1/ws, /v1/healthz 2026-05-01 13:53:16 -04:00
CarterPerez-dev a7865533cd feat(monitor/backend): ws hub, snapshot store, heartbeat collector with TDD 2026-05-01 07:09:28 -04:00
CarterPerez-dev bc9c2d06eb feat(monitor/backend): events, ratelimit, bus core packages with TDD 2026-05-01 07:03:47 -04:00
CarterPerez-dev 73c56333e6 feat(monitor/db): goose migrations 0001-0003 (alerts, panel data, collector state) 2026-05-01 06:28:51 -04:00
CarterPerez-dev 618aaf43e8 deps(monitor/backend): add coder/websocket, gobreaker/v2, errgroup, prometheus, testcontainers 2026-05-01 06:27:23 -04:00
CarterPerez-dev a627161b33 refactor(monitor/backend): rename module to monitor-the-situation/backend 2026-05-01 06:21:01 -04:00
CarterPerez-dev e46728bd80 feat(monitor/infra): three compose files, Makefile, .env.example 2026-05-01 06:17:28 -04:00
CarterPerez-dev a03c060b9f feat(monitor/nginx): base + dev + prod configs with WS upgrade routing 2026-05-01 06:16:12 -04:00
CarterPerez-dev 470533fd66 feat(monitor): infra scaffold — Dockerfiles, conf/ tree, .gitignore 2026-05-01 06:12:25 -04:00
Carter Perez 24c484386b
Update README.md 2026-04-29 03:44:54 -04:00
Carter Perez b826b6caed
Merge pull request #203 from CarterPerez-dev/chore/credential-rotation-enforcer-finish
Chore/credential rotation enforcer finish
2026-04-29 03:42:17 -04:00
CarterPerez-dev 91199476e1 feat: comprehensive audit-driven hardening pass
Closes 36 findings from the project audit. The headline win is correctness:
the daemon now actually does what the README claims, all three audit-log
integrity layers verify, and the rotation control loop terminates.

Critical fixes:
- Orchestrator bumps Credential.last_rotated_at on success and writes a
  sealed credential_versions row, so the policy evaluator stops re-firing
  the same rotation every tick
- Envelope encryption (AES-256-GCM, KEK-wrapped DEK, AAD-bound) is now
  wired into the rotation success path; the credential_versions table is
  no longer dead schema
- cre audit verify checks all three layers (hash chain + HMAC ratchet +
  Merkle batches) instead of only the chain
- BatchSealerScheduler runs every 300s and on shutdown so audit_batches
  actually fills with signed Merkle roots
- Compliance bundle exports the real audit_batches list and covers
  public_key.pem under the manifest's checksum
- cre run/watch hard-fail without CRE_HMAC_KEY_HEX + CRE_KEK_HEX rather
  than silently using zero defaults

Quality fixes:
- HTTP::Client wrapper with connect/read timeouts + bounded retry-with-
  jitter on 408/429/5xx; Telegram error logs redact the bot token
- EventBus dispatch isolates Block subscribers behind select+timeout so
  one stuck subscriber can't pin the bus
- RotationWorker checks rotations.in_flight before dispatching to dedupe
  duplicate schedules; PG enforces it at the DB via partial unique index
- Commit-step failures transition rotations to Inconsistent (a terminal
  state) and raise a critical alert instead of pretending success
- env_file rotator uses per-PID pending paths plus an exclusive flock on
  a sibling .lock file so two daemons can't race on the rename
- Versioned migration runner replaces the soup of CREATE IF NOT EXISTS;
  SQLite gains BEFORE UPDATE / BEFORE DELETE triggers on audit_events
- AuditSubscriber publishes a critical alert + panics by default when
  log writes fail, instead of silently dropping
- Engine.stop drains via an ack channel with a 2s deadline rather than
  the previous magic 50ms sleep
- Policy DSL moved into CRE::Policy::Dsl module (consumers `include` it);
  multiple matching policies raise PolicyConflictError
- Evaluator uses credentials.overdue() per-max_age group instead of
  loading every credential each tick
- AuditRepo gains each_in_range streaming and all_batches enumeration;
  bundle export streams instead of buffering
- New cre verify-bundle <zip> CLI re-runs every check the bundle README
  documents; new cre tui-demo for synthetic event preview

Tests: 179 unit (up from 159), all passing. New rotation_worker_spec,
new SigV4 regression vector, new orchestrator success-path coverage
(envelope write + last_rotated_at bump + Inconsistent state).

Docs: README + learn/00..04 aligned with the post-audit behavior;
removed /snooze (was a stub) and the AwsIamKey/Database CredentialKind
variants that had no rotators. Added required-env-var documentation.
2026-04-29 03:35:10 -04:00
Carter Perez d981f845d0
Merge pull request #202 from CarterPerez-dev/chore/haskell-reverse-proxy-finish
docs(roadmaps): replace mentorship CTAs with CertGames
2026-04-29 03:23:20 -04:00
CarterPerez-dev 9cde1df4a2 docs(roadmaps): replace mentorship CTAs with CertGames
Mentorship program is no longer offered. Swap top and bottom CTAs
across all 11 roadmaps (10 paths + README) to point to
certgames.com — practice tests / training games / AI tools.
2026-04-29 03:20:45 -04:00
Carter Perez 5e24202b50
Merge pull request #201 from CarterPerez-dev/dependabot/npm_and_yarn/PROJECTS/advanced/encrypted-p2p-chat/frontend/vite-7.3.2
chore(deps): bump vite from 5.4.21 to 7.3.2 in /PROJECTS/advanced/encrypted-p2p-chat/frontend
2026-04-29 03:20:15 -04:00
Carter Perez 5499804643
Update Base64.Encoder.Decoder.md 2026-04-29 03:19:37 -04:00
Carter Perez 3e921baf37
Update Makefile.example 2026-04-29 03:18:41 -04:00
dependabot[bot] 1529ad7cdc
chore(deps): bump vite in /PROJECTS/advanced/encrypted-p2p-chat/frontend
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.21 to 7.3.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-29 07:15:53 +00:00
Carter Perez 6ac93afb09
Update README.md 2026-04-29 03:14:28 -04:00
Carter Perez 4e6084694c
Merge pull request #200 from CarterPerez-dev/worktree-encrypted-p2p-chat-audit-fixes
Worktree encrypted p2p chat audit fixes
2026-04-29 03:13:21 -04:00
CarterPerez-dev 5adb079e76 docs(encrypted-p2p-chat): refresh learn/ for the audit-fixed architecture
Update all five learn documents to match the post-audit codebase:

- Replace references to the deleted server-side X3DH/Double Ratchet (`backend/app/core/encryption/`) with their client-side TypeScript counterparts (`frontend/src/crypto/{x3dh,double-ratchet}.ts`).
- Drop mentions of the removed `RatchetState` and `SkippedMessageKey` PostgreSQL models, the server-generated key paths, and the deprecated `send_encrypted_message` server-encryption method. Add an explicit "why we deleted server-side crypto" rationale in 02-ARCHITECTURE.
- Update API endpoint listings: drop `/encryption/initialize-keys`, `/encryption/rotate-signed-prekey`, `/encryption/opk-count`; add `/auth/me`, `/auth/logout`. Note that all non-auth routes require the session cookie.
- Reflect the new schema: `IdentityKey`, `SignedPrekey`, and `OneTimePrekey` now hold only public material; `User` carries a 64-byte `webauthn_user_handle`.
- Replace the `/ws?user_id=...` example with cookie-authenticated WebSocket usage.
- Remove all line-number references throughout (per the no-line-numbers feedback) — ~300 stripped via regex pass plus targeted prose rewrites.
- Update WebAuthn copy to reflect `UserVerificationRequirement.REQUIRED`, the per-credential-backup-eligibility flag, and challenge-bytes-keyed authentication storage.
- Refresh the file index and project tree to match the trimmed module layout.
2026-04-29 02:57:17 -04:00
Carter Perez 09542d3a83
Merge pull request #199 from CarterPerez-dev/chore/haskell-reverse-proxy-finish
Chore/haskell reverse proxy finish
2026-04-29 02:55:51 -04:00
CarterPerez-dev 6fd5f3d393 fix(encrypted-p2p-chat): apply audit findings — true E2EE, session auth, spec-correct crypto
This commit applies the actionable plan from docs/plans/2026-04-29-encrypted-p2p-chat-audit-fixes.md.

Backend
- Delete server-side X3DH/Double Ratchet (`app/core/encryption/*`); the server is now an opaque relay for client-encrypted messages.
- Drop `private_key` columns from IdentityKey/SignedPrekey/OneTimePrekey; prekey-bundle endpoint serves only public material.
- Remove RatchetState and SkippedMessageKey models and the deprecated server-encryption code path in message_service.
- Fix the broken `not OneTimePrekey.is_used` SQL filter (`Column.is_(False)` instead of Python `not`); repair the silent OPK-replenish amplification by deleting the server-generated path entirely.
- Stop calling `prekey_service.initialize_user_keys` from registration/login. Server never holds private key material again.
- Add session-cookie auth: opaque token issued in Redis on register/auth complete, `current_user` dependency on every protected route, `/auth/me`, `/auth/logout`. WebSocket authenticates via the same cookie.
- Add room membership checks to WS encrypted_message + typing handlers, GET/DELETE /rooms/{id}, GET /rooms/{id}/messages. broadcast_to_room now filters by participants, not presence.
- Add slowapi rate limits to register/auth/search; per-user sliding-window cap on WS messages.
- WebAuthn: replace username with a 64-byte random `webauthn_user_handle` for `user.id`; fix `backup_eligible` to read `credential_backup_eligible`; switch UserVerificationRequirement to REQUIRED; key authentication challenges by challenge bytes (no more "discoverable" collision).
- Implement GET /rooms/{id} and DELETE /rooms/{id} (cascade delete with ownership check) and fix the N+1 in list_rooms by batching User lookups.
- Remove broken/obsolete tests; add regression tests for the OPK filter bug and session-protected endpoints (401 without cookie).
- Sanitize WebSocket error responses; correlation IDs replace stack-trace leaks.

Frontend
- X3DH: prepend the 32-byte 0xff F prefix to the HKDF input per spec section 2.2.
- Double Ratchet: switch KDF_RK to spec form (HKDF salt=root_key, IKM=dh_output) and use 0x01/0x02 byte tags for KDF_CK.
- Replace base64 helpers in `crypto/primitives.ts` with the URL-safe codec from `lib/base64.ts` (single source of truth, no spread-stack hazard).
- Update auth.service to issue a session via cookies (no more dead token store), call `/auth/me` on app boot, clear crypto state on logout. Delete unused `session.store.ts`.
- Update room.service / Chat page to drop client-supplied user_id arguments; the cookie now identifies the caller.
- Better forward-secrecy UX text for messages predating the device.
- Add Vitest with X3DH and Double Ratchet round-trip + tamper-detection tests.

Tooling
- Add `slowapi>=0.1.9` to backend deps; add `vitest` to frontend devDeps with `pnpm test` script.
- justfile: `test-frontend`, `dev-reset`.
- Adjust `.gitignore` so the Python `lib/` rules no longer hide `frontend/src/lib`.

Migration: existing dev volumes hold private-key columns and KDF-incompatible ratchet states; run `just dev-reset` and re-register before hitting the new code path.
2026-04-29 02:30:10 -04:00
CarterPerez-dev 15f795f10c fix: extract Aenebris.Net.IP to dedupe SockAddr rendering (Finding 10)
- New module src/Aenebris/Net/IP.hs exposes
  sockAddrToIPBytes :: SockAddr -> ByteString.
  Single canonical implementation for IPv4 dotted-decimal, IPv6
  colon-hex (8 groups), and unix-socket "unix:<path>" rendering.

- Aenebris.RateLimit.clientIPKey reduces to
  `sockAddrToIPBytes . remoteHost`. Removes the local v6Bytes,
  the hostAddressToTuple/hostAddress6ToTuple/printf/showHex/
  intercalate imports, and the duplicated implementation.

- Aenebris.DDoS.ConnLimit drops its private copy of the same
  function. ipBytesFromSockAddr is kept as a thin alias for
  test backward-compatibility (= sockAddrToIPBytes), so existing
  callers and the connLimitSpec test continue to compile without
  rename churn.

- aenebris.cabal: expose Aenebris.Net.IP in the library stanza
  (now 30 modules total).

- test/Spec.hs: new netIpSpec asserts IPv4 dotted decimal,
  loopback, unix-socket prefix, and IPv6 colon-separated rendering
  (8 groups → 7 colons). Wires netIpSpec into main right after
  geoSpec.

362 examples passing, 0 failures, 0 GHC warnings.
2026-04-29 02:19:56 -04:00
CarterPerez-dev d9dd59db2a fix: close audit-pass-1 remaining MAJOR + quick MINOR (Findings 14, 19, 20, 28, 29)
- Geo.hs (Finding 14): geoAsnCounts is now bounded by
  defaultGeoAsnCountCap = 200_000. capAsnCounts is called inside
  bumpAsnCounter; on overflow, the entry with the oldest
  awWindowStart is evicted via Data.List.minimumBy + Data.Ord.comparing.
  Closes the unbounded-Map memory growth path. Uses minimumBy and
  comparing imports added to the existing module.

- ML/Middleware.hs, WAF/Engine.hs, Honeypot.hs (Finding 19): em
  dashes (\x2014) in user-facing response bodies and the generated
  robots.txt comment replaced with ASCII hyphens, per the project's
  guardrail-safe terminology rule.

- aenebris.cabal (Finding 20): copyright field updated from
  '2025 Carter Perez' to '2026 AngelaMos' to match the file headers.

- ML/IForest.hs (Finding 28): pathLength now respects a hard
  maxIForestDepth = 64 cutoff. Beyond that depth the function
  returns currentDepth + c(1) (= currentDepth) without further
  recursion, so a pathological tree built by a buggy fitter cannot
  blow the stack. Standard iForest depth is ceil(log2(256)) = 8, so
  the cap leaves >>8 generous headroom.

- ML/Model.hs (Finding 29): validateCategoricalNode now also
  rejects categorical thresholds that are not whole numbers. A
  threshold of 1.5 would silently floor to 1 today; with this
  change it is reported as a clear validation error instead.
  Real LightGBM never writes non-integer cat indices, so this is
  defense-in-depth against malformed exporters.

Build clean, 358 examples passing, 0 failures.
2026-04-29 02:15:14 -04:00
CarterPerez-dev 998b5268e0 fix: close audit-pass-1 MAJOR systemic findings (Findings 7-13)
Brings the older pre-ML modules up to the same standard as the
newer ML pipeline. Closes Findings 7 (file headers), 8 (inline
comments), 9 (NIH reimplementations), 11 (selectWeightedRR partial
function + O(n^2)), 12 (weak LoadBalancer tests), 13 (placeholder
Backend / ConnLimit tests), and 32 (locally-redefined HTTP status
constants).

Module rewrites with new file header + zero inline comments:
- src/Aenebris/Proxy.hs
- src/Aenebris/LoadBalancer.hs
- src/Aenebris/HealthCheck.hs
- src/Aenebris/Middleware/Security.hs
- src/Aenebris/Middleware/Redirect.hs
- src/Aenebris/Config.hs
- src/Aenebris/Backend.hs
- app/Main.hs

Algorithmic + structural fixes:
- LoadBalancer.selectWeightedRR no longer uses partial (!!) and a
  separate find/maximum/index pass; replaced with a single STM fold
  that tags each backend with its current weight and picks via
  Data.List.maximumBy + Data.Ord.comparing. O(n) instead of O(n^2),
  no Maybe fallback.
- Proxy.proxyApp deduplicates the regular-HTTP code path that was
  copy-pasted across the WebSocket and `_` connection-type branches
  (Finding 16). Single forwardRegular helper called from both.
- Proxy.logRequest removed; per-request stdout logging was
  inconsistent with the stderr error pattern used elsewhere and a
  perf hazard at scale (Finding 17). Defer real structured logging
  to Phase 4 per docs/status/MASTER_PLAN_2026.md.
- Backend.recordFailure now increments rbTotalFailures uniformly
  across all states (Finding 22), not just Unhealthy.
- Backend.Show instance now includes weight (Finding 21).
- Connection.hs gains microsPerSecond and httpOkStatusCode named
  constants. tcUpstreamReadSeconds added to TimeoutConfig.
- HealthCheck uses the shared microsPerSecond constant instead of
  inline 1000000 (Finding 18). Compares status against the named
  httpOkStatusCode constant.

NIH reimplementations replaced with stdlib:
- LoadBalancer: filterM, forM_ from Control.Monad; minimumBy,
  maximumBy from Data.List; comparing from Data.Ord.
- Proxy: zipWithM from Control.Monad.
- HealthCheck: zipWithM_ from Control.Monad.
- Middleware.Security: catMaybes from Data.Maybe.
- Config: nub from Data.List (replaces local nubText).
- Fingerprint.JA4H, ML.Features: built-in lookup over
  [(CI ByteString, ByteString)] (CI ByteString already has Eq).
- RateLimit, DDoS.ConnLimit: intercalate ":" from Data.List
  (replaces duplicated joinColons in two files).
- Honeypot: status200, status404 from Network.HTTP.Types (drops
  local mkStatus duplications).
- WAF.Engine: status403 from Network.HTTP.Types.

Test strengthening (test/Spec.hs):
- loadBalancerSpec: round-robin now asserts even distribution
  ([3,3,3] across 3 backends in 9 rounds); weighted RR asserts the
  4-weight backend wins >= 35 of 50 picks; least-connections asserts
  the lower-count backend is selected after manually bumping the
  other to 5 active connections.
- backendSpec: "tolerates repeated failures" replaced with a real
  state-transition test ('Healthy after 1 failure, Healthy after 2,
  Unhealthy after 3'). 'records successes without crashing'
  replaced with assertion that recordSuccess on Healthy resets
  rbConsecutiveFailures to 0.
- connLimitSpec: 'release decrements counter' now reads
  currentCount before and after release to assert the count
  actually went 1 -> 0.

Build clean, 358 examples passing, 0 failures, 0 GHC warnings on
any touched module. Audit findings 14, 15, 18 (residual MAJOR) and
19-31 (MINOR) deferred to subsequent passes.
2026-04-29 02:12:00 -04:00
CarterPerez-dev 2c7e743f57 fix: close audit-pass-1 CRITICAL findings (Findings 1-6)
Closes the six CRITICAL findings from the 2026-04-29 audit:

- Tunnel.hs (Findings 1+2+4):
  - connectToBackend now returns Either ConnectError Socket with
    proper handling of resolution failures, connect timeouts, and
    connect errors. Replaces the partial `error` call.
  - parseUpgradeStatus uses safe pattern matching on BS8.lines
    rather than `head $ BS8.lines` (closes the -Wx-partial warning).
  - receiveUpgradeResponse is bounded at 16 KB and uses a tight
    inner loop with strict accumulator (closes the unbounded-read
    DoS). Adds a 30s timeout via System.Timeout.timeout.
  - Adds attemptConnect with bracketOnError to ensure socket is
    closed on partial-failure paths.
  - File header + remove inline comments.

- TLS.hs (Findings 1+15):
  - loadCredentials replaced with credentialsOrDefault that
    returns empty credentials and logs to stderr on failure
    rather than crashing the SNI handler with `error`.
  - Cipher names updated to non-deprecated forms
    (cipher13_AES_128_GCM_SHA256 etc.) to clear -Wdeprecations.
  - File header + remove inline comments.

- Connection.hs (supporting Finding 3):
  - File header + add microsPerSecond and httpOkStatusCode named
    constants. Add tcUpstreamReadSeconds (default 30) to
    TimeoutConfig.

- Proxy.hs (Findings 1+3):
  - All five `error` calls replaced with hPutStrLn stderr +
    exitFailure (matching Main.hs's pattern).
  - forwardRequest wraps withResponse in System.Timeout.timeout
    sized off Connection.tcUpstreamReadSeconds. Returns 504 when
    upstream does not respond within budget.

- Main.hs (supporting Finding 3):
  - HTTP client Manager now configured with managerResponseTimeout
    set to tcUpstreamReadSeconds * microsPerSecond. Belt-and-
    suspenders with the application-layer timeout in Proxy.hs.

- ML/Loader.hs (Finding 5):
  - Added maxNumLeaves (4096) and maxNumTrees (10000) constants.
  - finalizeTree rejects num_leaves <= 0 and num_leaves > maxNumLeaves
    BEFORE allocating SoA arrays. Closes a crafted-model-file DoS.
  - runTrees threads a tree counter and rejects when the parsed
    count would exceed maxNumTrees.

- WAF/Rule.hs (Finding 6):
  - CompiledRegex changed from newtype to data with an extra
    !ByteString field carrying the original pattern.
  - Eq instance now compares by stored pattern bytes; reflexivity
    holds (x == x is True). Show instance now reveals the pattern
    for debugging.

- test/Spec.hs:
  - +2 WAF tests verifying Eq CompiledRegex reflexivity and
    pattern-distinguishing.
  - +2 ML.Loader tests verifying num_leaves bound rejection
    (above maxNumLeaves and at zero).

358 total examples passing, 0 failures, 0 GHC warnings on touched
modules. Audit findings 7-32 (MAJOR systemic + remaining MAJOR +
MINOR) are deferred to subsequent passes per the audit plan.
2026-04-29 01:59:36 -04:00
Carter Perez 3f9bb07096
Update DEMO.md 2026-04-29 01:56:31 -04:00
Carter Perez 21601d4759
Merge pull request #198 from CarterPerez-dev/chore/haskell-reverse-proxy-finish
feat: add Aenebris.ML.Inference and Aenebris.ML.Calibration
2026-04-29 01:54:50 -04:00
CarterPerez-dev 99d89b3050 docs(README): match repo project pattern (#27 intermediate)
Replace MIT placeholder with the standard intermediate-project README
shape used by sbom-generator, secrets-scanner, etc:
- Block-character ASCII banner (CRE)
- Project #27 / Crystal / AGPLv3 / Postgres badges
- One-line description quote -> learn modules pointer
- What It Does (bulleted feature list)
- Quick Start with both clone-and-build and install.sh paths
- just hint with install one-liner
- Demo tiers + daemon usage code blocks
- Flagship Rotators table
- ASCII architecture diagram
- Three-layer audit log diagram (own section)
- Stack section (language + deps + FFI note + testing)
- Configuration pointer (CONFIGURATION.md)
- Learn modules table
- AGPL 3.0 license footer

Also swapped LICENSE from MIT to AGPL v3 to match the rest of the
intermediate projects.
2026-04-29 01:50:30 -04:00
CarterPerez-dev d3dd5f2dcb fix(encrypted-p2p-chat): repair dev-up after surrealdb 3.x and pnpm migration
- Switch frontend Dockerfiles to pnpm via corepack (project uses pnpm-lock.yaml)
- Pin SurrealDB to v3.0.5 and switch datastore URL from `file:` to `rocksdb:`
- Add idempotent SurrealDB schema bootstrap on connect to prevent NotFoundError on first SELECT against schemaless tables in 3.x
2026-04-29 01:46:36 -04:00
CarterPerez-dev 225716d140 fix(env_file): rename _s -> s on commit/rollback_apply to match parent
Crystal's compiler warns when an overriding method's parameter name
differs from the parent's, since named-argument callers would pass
the wrong slot. Underscore-prefix was meant as 'unused' but it changed
the parameter name, which Crystal correctly flagged.

Match the parent signature (s : Domain::NewSecret) and use the explicit
'_ = s' idiom inside the body to mark it intentionally unused. Now
'shards build cre' compiles with zero warnings.
2026-04-29 01:43:56 -04:00
CarterPerez-dev 1b8cfc9589 feat: wire RotationWorker + Telegram in cre run + CONFIGURATION.md guide
Two changes that go together:

1. RotationWorker (src/cre/engine/rotation_worker.cr) is the missing
   subscriber that turns RotationScheduled events into actual 4-step
   rotations. Without it, the daemon ticked, evaluated, fired
   RotationScheduled, and... nothing happened. Now it dispatches via
   the orchestrator using a kind -> Rotator dispatch table populated
   at boot.

2. cre run now wires:
   - All 4 rotators based on env vars (AWS_ACCESS_KEY_ID, VAULT_ADDR/TOKEN,
     GITHUB_TOKEN). EnvFile is always available.
   - Telegram subscriber + bot if TELEGRAM_TOKEN + chat-id env vars set.
   Boot output reports which rotators wired and whether telegram is on.

3. CONFIGURATION.md: full operator setup guide. 13 sections covering
   required env vars, Postgres setup with role hardening, key generation,
   policy authoring, inventory seeding (per-rotator tag schemas), AWS IAM
   policy, Vault token policy.hcl, GitHub admin PAT acquisition, Telegram
   bot creation + chat ID discovery, systemd unit with hardening
   directives, audit chain verification, KEK rotation procedure, and a
   13-item production security checklist.
2026-04-29 01:42:45 -04:00
CarterPerez-dev f156941cd8 docs(encrypted-p2p-chat): add DEMO.md with UI screenshots
Walks through the full flow — passkey registration, passkey login,
new conversation lookup, empty thread, first message, end-to-end
chat, and mobile view — matching the format used by the bug-bounty
and SIEM project demos.
2026-04-29 01:42:05 -04:00
CarterPerez-dev 7835aa4dc5 feat(demo): add tui-demo command + fix demo-full recipe
cre tui-demo (8 seconds default) renders the live TUI with synthetic
events: 4 fake credentials going through rotation states, a policy
violation, drift detection, an apply-step failure, and a critical alert.
Lets you see what watch mode looks like without seeding any real data.

demo-full: dropped the bogus --config=... flag (cre run never
implemented config-file loading; uses env vars + --db). Now exports
the four service URLs (DATABASE_URL, AWS_ENDPOINT, VAULT_ADDR,
GITHUB_API_BASE) at the user's manually-set ports (6022/45666/18201/7115)
and runs the daemon against the real Postgres. Added demo-full-status
to peek at container health.
2026-04-29 01:34:25 -04:00
CarterPerez-dev 73e3488edc chore: replace Makefile with justfile to match repo convention
Justfile mirrors the haskell-reverse-proxy pattern (grouped recipes:
build, run, demo, test, quality, db, util). 33 recipes covering full
lifecycle - including parameterized recipes (cre rotate ID, cre policy
show NAME) that read more naturally as 'just rotate ...' than as
make targets.

User-edited docker-compose.yml port mappings preserved
(postgres:18 on 6022, localstack on 45666, vault on 18201,
fake-github on 7115).
2026-04-29 01:28:37 -04:00
CarterPerez-dev a40d064009 fix(credential-enumeration): rename .nimble to credenum.nimble
Nim package names cannot contain hyphens, so `nimble install -y nph`
in the lint workflow aborted on `validatePackageName` before nph could
be installed, causing the Nim lint job to fail with `nph: command not
found`. Renaming the manifest to match the binary name resolves it
and lets contributors run nimble inside the project.
2026-04-29 01:23:40 -04:00
CarterPerez-dev 33cddb57cd fix(demo): wire AuditSubscriber so events reach the audit log
Tier 1 demo was creating an AuditLog instance but never subscribing it
to the bus, so the verification step always reported '0 events'.
Now we explicitly start the AuditSubscriber and the verifier reports
the actual chain length (4 step events + RotationCompleted = 5).
2026-04-29 01:22:18 -04:00
CarterPerez-dev 51287d3f7d docs: install.sh + learn/ walkthrough series + full README
install.sh: cross-platform Crystal version check, libpcre2 dependency
hint, shards install + release build, optional install to PATH via
INSTALL_TO_PATH=1.

learn/:
  00-OVERVIEW.md       - quick start + three-tier demo path
  01-CONCEPTS.md       - rotation theory, real breaches (SolarWinds,
                         Codecov, LastPass, Storm-0558, Snowflake/UNC5537),
                         framework controls
  02-ARCHITECTURE.md   - bus + plugin diagrams, three-layer audit integrity,
                         AEAD envelope encryption layer breakdown
  03-IMPLEMENTATION.md - code walkthrough pointing at each key file
  04-CHALLENGES.md     - 10 ranked extension challenges (PG ALTER USER
                         rotator, Slack notifier, ML-KEM hybrid wrap,
                         OpenTimestamps anchoring, SPIFFE workload identity,
                         JIT credential broker, etc.)

README.md: full hero + badges + quick start + subcommand cheatsheet +
flagship rotator table + ASCII architecture diagram + project layout
+ links to learn/ + test suite instructions.
2026-04-29 01:21:17 -04:00
CarterPerez-dev a19e8669d2 feat(demo): Tier 1 zero-deps demo + Tier 2 docker-compose scaffold
Tier 1 (cre demo): in-memory SQLite + tempfile .env rotator. Walks
through inventory -> before -> 4-step rotation -> after -> audit chain
verify. Narrates every step event live with green/red glyphs. Runs in
under 1 second on commodity hardware, no external deps.

Tier 2 (docker/docker-compose.yml): postgres:16, localstack
(secretsmanager), hashicorp/vault dev mode, plus a fake-GitHub Flask
app that mocks /user, POST/DELETE /user/personal-access-tokens.
config/demo-full.cr.example documents env vars for the full stack.

Also fixed env_file rotator newline preservation (lines(chomp: true)
+ explicit \n join) so the rotated file is properly delimited.
2026-04-29 01:16:08 -04:00
CarterPerez-dev 7ce0cb48bd feat(compliance): evidence bundle export + framework control mapping
ControlMapping for SOC2 (CC6.1, CC6.6, CC6.7, CC4.1, CC7.x), PCI-DSS
(8.3.9, 8.6.3, 10.5.x, 3.7.4), ISO 27001:2022 (A.5.16, A.5.17, A.5.18,
A.8.5, A.8.15, A.8.16, A.8.24), HIPAA (164.308 / 164.312). Mapping is
intentionally minimal - each event maps only where it provides direct
evidence.

Bundle.write produces a self-verifying ZIP:
- audit_log.ndjson (raw chain rows with hex hashes)
- audit_batches.json (signed Merkle roots)
- control_mapping.json (event_type -> controls)
- manifest.json (per-file SHA-256 + size)
- README.md (verification instructions)
- public_key.pem + manifest.sig (Ed25519, when signer provided)

7 specs verify ZIP layout, manifest sha256-per-file, control mapping
content per framework.
2026-04-29 01:14:02 -04:00
CarterPerez-dev 545e189b43 feat(cli): subcommand dispatch with 9 commands + output formatters
CLI grammar:
  cre run                    daemon (sqlite or postgres)
  cre watch                  daemon + live TUI in same process
  cre check                  one-shot policy eval (CI-friendly exit codes)
  cre rotate <id>            manual rotation
  cre policy list / show     compile-time-baked policy registry inspection
  cre export --framework=X   compliance bundle (Phase 14 stub)
  cre audit verify           hash chain + HMAC + Merkle integrity verification
  cre demo                   Tier 1 demo (Phase 15 stub)
  cre version                version
  cre help                   usage

Output module: human / json / ndjson formatter; CI-friendly exit codes
(0 ok, 1 violations/error, 64 usage, 2 audit-chain-broken).

run/watch wire engine + persistence + scheduler + evaluator + log
notifier (and TUI for watch); SIGINT triggers graceful shutdown.

7 unit specs cover usage / version / policy-list-empty / policy-show-404
/ unknown-subcommand / check-no-violations. All commands callable
end-to-end against a working binary.
2026-04-29 01:12:46 -04:00
CarterPerez-dev 9651ff56f1 feat(tui): hand-rolled live monitor with ANSI primitives + 4 panels
Stack:
- Ansi: stdlib-only escape helpers (move, colorize, strip)
- State: rolling view of active rotations + recent events; trims to N
- Renderer: paints header / status / active / recent panels; works against
  any IO so tests assert against IO::Memory
- Tui: ties State + Renderer to event bus; coalesces repaints to a tick
  interval (default 200ms) to avoid flicker under burst events

Active rotation rows show step progress as a filled-bar (▰▰▰▱). Recent
events list latest 8 with timestamp + severity glyph (✓ / ⚠ / !). The
renderer's pad/center helpers correctly account for ANSI escape widths
when computing visible column width.

9 specs cover ANSI helpers (color codes, cursor move, strip), State
event-to-row mapping with completion bookkeeping and ring-buffer trim,
and Renderer output assertions across empty + active + populated states.
2026-04-29 01:09:20 -04:00
CarterPerez-dev 67d65a934f feat(notifiers): structured-log subscriber + Telegram bidirectional bot
LogNotifier subscribes (Drop overflow) and emits stdlib Log lines with
structured kwargs (credential_id, rotation_id, severity, etc.) suitable
for downstream ingestion by journald/vector/fluentd.

Telegram is a thin HTTP::Client wrapper for sendMessage and getUpdates.
TelegramSubscriber sends emoji-prefixed alerts on RotationFailed,
DriftDetected, PolicyViolation, AlertRaised; success notifications gated
by the notify_on_success flag. Errors are swallowed so a flaky bot never
blocks the engine.

TelegramBot does long-polling getUpdates and dispatches commands:
- viewer tier: /status /queue /history /alerts /help
- operator tier: viewer + /rotate + /snooze
ACL is by chat_id allowlist (bot token + chat IDs in env vars).
/rotate publishes RotationScheduled to the bus.

11 unit specs cover send_message + error handling + getUpdates parsing,
subscriber dispatch, success-suppression flag, and bot ACL enforcement
(unauthorized / viewer-blocked-from-mutations / operator-can-rotate).
2026-04-29 01:07:44 -04:00
CarterPerez-dev d7d5f10825 feat(engine): Scheduler fiber publishes SchedulerTick on a fixed interval
Initial tick fires immediately on start so first policy evaluation
doesn't wait for the full interval on boot. The PolicyEvaluator
subscribes to SchedulerTick events and invokes evaluate_all -> overdue
discovery -> RotationScheduled fan-out. 3 specs verify boot-tick,
periodic cadence, and clean stop().
2026-04-29 01:05:44 -04:00
CarterPerez-dev 873257ad4b feat(rotators): GitHub fine-grained PAT rotator + thin GitHub API client
Github::Client wraps the bearer-auth REST API with required
'X-GitHub-Api-Version: 2022-11-28' header. Implements me, create_pat
(POST /user/personal-access-tokens), delete_pat (DELETE by id).

GithubPatRotator's 4-step contract:
- generate: create_pat returns new id + token value (fresh PAT alongside old)
- apply: no-op (create_pat already active)
- verify: probe-client uses the NEW PAT to GET /user; success means it works
- commit: delete OLD PAT by id (tracked via old_pat_id tag)
- rollback_apply: delete NEW PAT

7 unit specs verify create/delete/me round-trips, full rotation path,
verify-on-401, and rollback deletion.
2026-04-29 01:05:10 -04:00
CarterPerez-dev d431e9014e feat(rotators): Vault dynamic-secrets rotator + thin Vault client
Vault::Client wraps token-auth REST: read_dynamic, revoke_lease,
renew_lease, health. Errors surface as VaultError carrying status.

VaultDynamicRotator's rotation contract leans on Vault as the secret
factory:
- generate: read_dynamic (Vault issues new creds + lease)
- apply: no-op (Vault already provisioned)
- verify: lease renewal acts as liveness check
- commit: revoke OLD lease (tracked in current_lease_id tag)
- rollback_apply: revoke NEW lease

8 unit specs cover client method round-trips, rotator full path with
old-lease revocation, verify-on-Vault-error handling, rollback, and
the no-old-lease pass-through case.
2026-04-29 01:04:12 -04:00
CarterPerez-dev a4a9e1170a feat: add Aenebris.ML.Middleware (Wai integration for ML pipeline)
- New Aenebris.ML.Middleware module: Wai middleware that wires the
  Aenebris.ML.Engine into the per-request pipeline. Per request:
    1. Run extractFeatures (FeatureContext + Request) -> FeatureVector
    2. Convert via featureVectorToVector -> VU.Vector Double
    3. runEngine eng fv -> DecisionDetails
    4. Route by ddDecision:
       DecisionHuman     -> pass through to inner Application,
                            optionally attach X-Aenebris-ML-Decision
                            and X-Aenebris-ML-Score response headers
       DecisionBot       -> 403 + text/plain block body
       DecisionChallenge -> 403 + text/html challenge page
  All response builders are operator-overridable via the
  MLMiddlewareConfig record. Optional logging callback fires once
  per request for structured logs / metrics integration.

- defaultBotResponse, defaultChallengeResponse: sensible defaults
  matching the existing WAF middleware pattern (403 + descriptive
  body). decisionResponseHeader / scoreResponseHeader are exported
  so operators can build custom responses that still surface the
  signal headers.

- 12 tests via Network.Wai.Test.runSession covering: pass-through
  on Human, 403 on Bot, 403 on Challenge with text/html body,
  signal-header attachment toggle, custom response builder override,
  logging callback invocation count.

- aenebris.cabal: expose Aenebris.ML.Middleware in the library stanza.
- test/Spec.hs: add modifyTVar' to the Control.Concurrent.STM import
  list for the logging-callback test.

- 354 total examples passing, 0 GHC warnings on the new module.

This closes the core Phase 2.5 ML pipeline. The full sequence
(Features -> Model -> Loader -> Inference -> Calibration -> IForest
-> Engine -> Middleware) is now fully implemented and integration-
tested. A hosted Aenebris instance can now be configured with a
trained LightGBM model + optional calibrator + optional IForest
and will block bots, challenge ambiguous traffic, and pass humans
on every HTTP request.
2026-04-29 01:04:01 -04:00
CarterPerez-dev 7e23f58fbb feat(rotators): AWS Secrets Manager rotator with SigV4 signer + secrets client
SigV4 implementation per AWS reference: canonical request -> string-to-sign
-> HMAC-derived signing key (kSecret -> kDate -> kRegion -> kService ->
kSigning) -> HMAC-SHA256 signature. Includes session token (STS) support.

SecretsManagerClient wraps PutSecretValue, GetSecretValue,
UpdateSecretVersionStage with custom endpoint support (LocalStack).
AWS API errors surface as AwsApiError carrying HTTP status + AWS __type.

AwsSecretsRotator implements 4-step contract:
- generate: PutSecretValue with AWSPENDING stage, captures version_id
- apply: no-op (PutSecretValue already exposed it)
- verify: GetSecretValue by version_id, byte-equal SecretString check
- commit: UpdateSecretVersionStage move AWSCURRENT to new + remove from old
- rollback_apply: UpdateSecretVersionStage remove AWSPENDING from new version

13 unit specs verify SigV4 idempotence + format, client methods (with
WebMock), rotator's full 4-step path, verify-mismatch, rollback_apply,
and can_rotate? gating.
2026-04-29 01:02:40 -04:00