Skip to content

feat(profiles): add stateless profile context handles - #380

Merged
mohanagy merged 3 commits into
developmentfrom
feat/377-stateless-profile-context
Aug 11, 2026
Merged

feat(profiles): add stateless profile context handles#380
mohanagy merged 3 commits into
developmentfrom
feat/377-stateless-profile-context

Conversation

@mohanagy

@mohanagy mohanagy commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Closes #377

Outcome

  • adds short-lived opaque AES-GCM profile-context handles bound to verified issuer, subject, audience, chat, deployment, profile, expiry, and monotonic key epoch
  • adds deployment-wide revocation/keyring interfaces, audited replacement-before-revocation ordering, separately keyed audit correlation, and fixed fail-closed errors
  • threads explicit request-scoped selection through modern tools, resources, and prompts without mutating legacy active-profile state
  • strips and rejects bearer copies before audit or upstream forwarding; modern mode requires trusted authInfo and a concrete audit journal
  • keeps tools/list independent of prior selections and preserves the legacy stdio/session-aware HTTP path
  • exposes the supported host API and verifies it from the installed npm tarball

Validation

  • npm test: 153 files, 1,898 passed, 34 skipped
  • npm run test:coverage: 96.01% lines overall; profile-context boundary 99.07% lines, 94.56% branches, 100% functions
  • npm run typecheck
  • npm run lint
  • npm run build
  • npm run check:pack
  • npm run test:package
  • packaged legacy client plus two-instance modern client compatibility, cross-chat rejection, replacement/revocation, resource metadata stripping, prompt-smuggling rejection, stable catalog, and bearer-free audit/result checks

Summary by CodeRabbit

  • New Features

    • Added opt-in stateless profile-context support for authenticated profile selection across requests and server instances.
    • Added encrypted, deployment-bound handles with expiration, revocation, replacement, key rotation, audit correlation, and binding validation.
    • Added strict discovery, metadata protection, standardized errors, and profile switching while preserving legacy session behavior.
    • Added public APIs, runtime options, error codes, and CLI mappings.
  • Documentation

    • Documented configuration, APIs, security behavior, auditing, errors, and legacy compatibility.
  • Tests

    • Added comprehensive coverage for handles, runtime behavior, exports, documentation, revocation, expiration, and security validation.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ccfa09e1-7f8b-4acd-a892-0490ae0837c3

📥 Commits

Reviewing files that changed from the base of the PR and between 9446f6c and 751ebb6.

📒 Files selected for processing (1)
  • tests/stateless-profile-context-runtime.test.ts

📝 Walkthrough

Walkthrough

The PR adds encrypted, authenticated, revocable profile-context handles. Modern MCP requests resolve and sanitize these handles across tools, resources, prompts, and profile operations. Audit correlation, runtime options, public exports, documentation, and compatibility tests are updated.

Changes

Modern stateless profile-context boundary

Layer / File(s) Summary
Encrypted handle lifecycle
src/profiles/profile-context-handle.ts, tests/profile-context-handle.test.ts, vitest.config.ts
Adds AES-256-GCM handles, key rotation, expiry, binding validation, fail-closed errors, revocation storage, and coverage thresholds.
Modern request resolution and MCP integration
src/mcp/server/miftah-server.ts, src/runtime/create-miftah-runtime.ts, src/profiles/profile-manager.ts, src/utils/errors.ts, tests/stateless-profile-context-runtime.test.ts
Adds authenticated handle resolution, metadata stripping, profile-aware MCP operations, schema decoration, replacement handles, strict discovery, and legacy locking isolation.
Audit and operation correlation
src/audit/*, src/mcp/server/operation-pipeline.ts, src/cli/exit-codes.ts, tests/cli-exit-codes.test.ts
Propagates profile-context correlation through audit and selection records and maps profile-context errors to CLI exit categories.
Public API and compatibility validation
src/index.ts, docs/*, CHANGELOG.md, tests/package-contract.test.ts, tests/public-api.test.ts, tests/profile-context-handle-docs-contract.test.ts, package.json
Exports and documents the new APIs. Tests cover packaged consumers, cross-instance handling, revocation, isolation, redaction, legacy compatibility, and documentation requirements.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

  • mohanagy/miftah#378 — Introduces the stateless profile-context design implemented here.
  • mohanagy/miftah#379 — Provides the authenticated request-context boundary used for handle binding.
  • mohanagy/miftah#60 — Shares server, operation-pipeline, and audit propagation changes.

Suggested labels: security, area:mcp, area:routing

Poem

A rabbit seals a handle tight,
Bound to chat and profile right.
Old keys turn and tokens cease,
Audit trails record the peace.
Metadata drops before the hop,
Legacy paths continue to operate.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Docstring Coverage ❌ Error Docstring coverage is 2.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: stateless profile-context handles.
Description check ✅ Passed The description explains the change, security behavior, and validation results, although it uses Outcome instead of the template's Summary and Security impact headings.
Linked Issues check ✅ Passed The implementation and reported tests address the linked issue's handle, binding, revocation, audit, isolation, discovery, compatibility, and legacy-path requirements.
Out of Scope Changes check ✅ Passed The code, documentation, changelog, API exports, and tests directly support the linked issue and its production integration requirements.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/377-stateless-profile-context

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added area:mcp MCP protocol server and proxy behavior area:routing Profile routing and matchers security Security, trust boundary, or credential-safety work labels Aug 11, 2026
coderabbitai[bot]
coderabbitai Bot previously requested changes Aug 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/mcp/server/miftah-server.ts (2)

1007-1041: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Modern profile-context rejections bypass the audit journal in every MCP handler. Handle resolution and request authentication run before runAudited opens an audit scope. A failed authentication, or a tampered, expired, or revoked handle, therefore terminates the request with no audit event. The modern mode requires a configured audit journal, and the constructor enforces that at lines 370 to 378, so a silent rejection contradicts the stated guarantee and hides handle-probing activity from operators.

  • src/mcp/server/miftah-server.ts#L1007-L1041: open the audit scope before prepareCall, and finish it with the normalized error code when prepareCall throws, instead of returning textResult at line 1013.
  • src/mcp/server/miftah-server.ts#L1063-L1103: move the modernRequestProfileState call inside runAudited, or open a scope beforehand, for the resource-template and subscribe handlers, and apply the same change at lines 1133, 1163, 1200, 1230, and 1267.

Use the configured default profile as sourceProfile when no handle resolves, because the caller's profile is unknown at that point.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mcp/server/miftah-server.ts` around lines 1007 - 1041, The MCP handlers
must audit modern profile-context failures before request preparation or
authentication can reject them. In src/mcp/server/miftah-server.ts:1007-1041,
open runAudited before prepareCall, use the configured default profile as
sourceProfile when resolution fails, and complete the audit with the normalized
error code instead of returning directly; in
src/mcp/server/miftah-server.ts:1063-1103 and the handlers at lines 1133, 1163,
1200, 1230, and 1267, move modernRequestProfileState inside runAudited or open
an equivalent scope beforehand, applying the same normalized rejection auditing.

977-1005: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Require strict discovery for modern profile contexts. tools/list advertises the default or locked profile, while tools/call resolves the handle's profile. Since "permissive" is the default, a non-default handle can receive TOOL_NOT_FOUND for an advertised tool. Force "strict" mode or require identical client-visible tool names and schemas before enabling modern profile contexts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mcp/server/miftah-server.ts` around lines 977 - 1005, Update
registerHandlers and the modern profile-context discovery flow so tools/list
uses strict discovery rather than advertising permissive/default-profile tools;
alternatively, gate modern profile contexts until client-visible tool names and
schemas are identical across profiles. Ensure tools/call cannot resolve a handle
profile that differs from the profile used to advertise the tool.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/mcp/server/miftah-server.ts`:
- Around line 3327-3334: Update the sanitized argument construction in the loop
building args to avoid prototype-mutating assignment for payload keys such as
__proto__. Initialize args with a null prototype or copy each key via
Object.defineProperty, while preserving the existing filtering of
PROFILE_CONTEXT_ARGUMENT and downstream frozen-object behavior.
- Around line 1867-1887: Bind modern profile transition approvals to the
authenticated request context. Update the call site of
requireModernProfileTransitionConfirmation to pass modern.authenticated, extend
that method’s context handling as needed, and include
modern.authenticated.auditCorrelation in the requireApproval binding alongside
the existing profile fields so concurrent requests cannot reuse each other’s
approvals.
- Around line 946-962: Update normalizeProfileContextError so only unrecognized
errors that are instances of ProfileContextHandleError map to
PROFILE_CONTEXT_INVALID; classify all other unknown errors, including plain
infrastructure Error values, as PROFILE_CONTEXT_UNAVAILABLE. Preserve the
existing mappings for known context error codes and MiftahError instances.

In `@src/profiles/profile-context-handle.ts`:
- Around line 368-377: Update the interface documentation for keyringProvider
near lines 68–69 to state that it is invoked once per mint, resolve, and revoke
operation and twice during replace, so hosts must serve a short-TTL cached
keyring snapshot and enforce their own timeout. Do not change the keyring()
implementation.
- Around line 381-392: Document in the key-manager operating procedure that
ProfileContextHandle’s highestActiveEpoch and highestActiveKey protections are
limited to a single process instance and reset on restart. State that the key
manager must reject epoch rollbacks and same-epoch key replacement at the
source, and define monitoring for epoch regressions.
- Around line 441-448: Update revoke to call the existing prune method before
checking maximumEntries, using Date.now() as the pruning timestamp. Keep the
subsequent capacity check and expirations.set behavior unchanged so expired
entries are reclaimed before deciding that capacity is unavailable.

In `@tests/profile-context-handle.test.ts`:
- Around line 243-273: Add a test covering revocation-store failure within
replace after commitAudit succeeds. Configure shared keyring and a
revocations.revoke failure, assert replace rejects with
PROFILE_CONTEXT_UNAVAILABLE, verify the audit callback ran, and confirm the
prior handle remains usable without exposing a replacement.
- Line 499: Replace the probabilistic minted.handle substring assertion in the
relevant profile-handle test with a deterministic property check: decode the
ciphertext segment and verify the plaintext profile name is absent from the raw
bytes, or reuse the existing randomBytes seam to mint a fixed handle. Preserve
the test’s intent of verifying that the profile name is not exposed in
plaintext.
- Around line 115-119: Update the assertion loops over invalidInputs and the
corresponding loops near the other referenced locations to include the current
input’s index or a descriptive label in each assertion, so failures identify
which case failed while preserving the existing validation expectations.
- Around line 406-414: Add malformed test cases to the `malformed` array using
valid base64url strings that decode to incorrect byte lengths for the
initialization-vector and authentication-tag parts, rather than `"!"`. Ensure
these cases exercise the `decodePart` exact-length validation for both envelope
components while preserving the existing malformed inputs.

---

Outside diff comments:
In `@src/mcp/server/miftah-server.ts`:
- Around line 1007-1041: The MCP handlers must audit modern profile-context
failures before request preparation or authentication can reject them. In
src/mcp/server/miftah-server.ts:1007-1041, open runAudited before prepareCall,
use the configured default profile as sourceProfile when resolution fails, and
complete the audit with the normalized error code instead of returning directly;
in src/mcp/server/miftah-server.ts:1063-1103 and the handlers at lines 1133,
1163, 1200, 1230, and 1267, move modernRequestProfileState inside runAudited or
open an equivalent scope beforehand, applying the same normalized rejection
auditing.
- Around line 977-1005: Update registerHandlers and the modern profile-context
discovery flow so tools/list uses strict discovery rather than advertising
permissive/default-profile tools; alternatively, gate modern profile contexts
until client-visible tool names and schemas are identical across profiles.
Ensure tools/call cannot resolve a handle profile that differs from the profile
used to advertise the tool.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3acf2ca3-e096-476d-8341-8f0eea13160d

📥 Commits

Reviewing files that changed from the base of the PR and between fcf8641 and 65f8858.

📒 Files selected for processing (21)
  • CHANGELOG.md
  • docs/library-api.md
  • docs/plans/2026-08-11-stateless-profile-context-decision.md
  • package.json
  • src/audit/audit-trail.ts
  • src/audit/audit-types.ts
  • src/cli/exit-codes.ts
  • src/index.ts
  • src/mcp/server/miftah-server.ts
  • src/mcp/server/operation-pipeline.ts
  • src/profiles/profile-context-handle.ts
  • src/profiles/profile-manager.ts
  • src/runtime/create-miftah-runtime.ts
  • src/utils/errors.ts
  • tests/cli-exit-codes.test.ts
  • tests/package-contract.test.ts
  • tests/profile-context-handle-docs-contract.test.ts
  • tests/profile-context-handle.test.ts
  • tests/public-api.test.ts
  • tests/stateless-profile-context-runtime.test.ts
  • vitest.config.ts

Comment thread src/mcp/server/miftah-server.ts
Comment thread src/mcp/server/miftah-server.ts
Comment thread src/mcp/server/miftah-server.ts
Comment thread src/profiles/profile-context-handle.ts
Comment thread src/profiles/profile-context-handle.ts
Comment thread src/profiles/profile-context-handle.ts Outdated
Comment thread tests/profile-context-handle.test.ts Outdated
Comment thread tests/profile-context-handle.test.ts
Comment thread tests/profile-context-handle.test.ts
Comment thread tests/profile-context-handle.test.ts Outdated
@mohanagy

Copy link
Copy Markdown
Owner Author

Addressed the stateless-context review in 9446f6c.

  • Modern tools, resources, and prompts now enter the fail-closed audit barrier before authentication, handle extraction, or resolution; rejected requests record fixed error codes against the configured fallback profile, and successful resolution updates the audit to the authenticated profile/correlation.
  • Modern mode now always performs strict cross-profile tool discovery, preventing tools/list and tools/call surface drift.
  • Profile transition approvals include the authenticated chat correlation in their keyed binding.
  • Known invalid auth/context failures remain invalid; unknown infrastructure faults are unavailable.
  • Reserved-argument stripping uses own data-property definitions, including for proto, and tool-name bearer smuggling is rejected before audit.
  • Keyring call frequency, host timeout/cache duties, and process-local epoch rollback memory are documented.
  • Revocation capacity pruning uses the validated injected operation clock passed to ProfileContextRevocationStore.revoke, rather than Date.now, preserving custom-clock correctness.
  • Added adversarial coverage for rejected-request auditing, implicit strict discovery, replacement revocation failure, exact envelope lengths, deterministic ciphertext inspection, bounded-store reclamation, and labeled malformed cases.

Validation: npm test (153 files; 1,900 passed, 34 skipped), npm run test:coverage (95.97% lines / 92.15% branches overall; profile-context handle 98.53% lines / 94.19% branches / 100% functions), typecheck, lint, build, check:pack, and test:package (31/31) all pass locally.

coderabbitai[bot]
coderabbitai Bot previously requested changes Aug 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/stateless-profile-context-runtime.test.ts`:
- Around line 286-292: Add an explicit assertion in the prototypeArguments test
that the relevant object prototype remains unpolluted after calling the echo
tool, alongside the existing echoed-text assertion. Target the behavior
protected by extractProfileContext and verify the injected polluted property is
absent rather than relying only on the response content.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bb467dae-513e-45a8-b6a1-c3254dceb099

📥 Commits

Reviewing files that changed from the base of the PR and between 65f8858 and 9446f6c.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • docs/library-api.md
  • docs/plans/2026-08-11-stateless-profile-context-decision.md
  • src/audit/audit-trail.ts
  • src/mcp/server/miftah-server.ts
  • src/profiles/profile-context-handle.ts
  • tests/profile-context-handle.test.ts
  • tests/stateless-profile-context-runtime.test.ts

Comment thread tests/stateless-profile-context-runtime.test.ts
@mohanagy

Copy link
Copy Markdown
Owner Author

Final review follow-up is in 751ebb6e791953ee782c3aaf2305171227be7749: the prototype-smuggling integration case now explicitly asserts Object.prototype remains unpolluted. Focused runtime tests, typecheck, lint, and diff checks passed locally. Exact-head CI run https://github.com/mohanagy/miftah/actions/runs/31489559764 is fully green, CodeRabbit reports success on this head, and all review threads are resolved.

@mohanagy
mohanagy dismissed stale reviews from coderabbitai[bot] and coderabbitai[bot] August 11, 2026 12:12

All actionable findings were addressed on later commits; exact-head CodeRabbit and CI checks are green, and all review threads are resolved.

@mohanagy
mohanagy merged commit 5c6a650 into development Aug 11, 2026
12 checks passed
@mohanagy
mohanagy deleted the feat/377-stateless-profile-context branch August 11, 2026 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:mcp MCP protocol server and proxy behavior area:routing Profile routing and matchers security Security, trust boundary, or credential-safety work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Profiles] Implement opaque cross-instance stateless profile-context handles

1 participant