Skip to content

chore: remove jwt from RequestContext#2118

Merged
ehayes2000 merged 1 commit intomainfrom
eric/remove-jwt-from-request-context
Mar 25, 2026
Merged

chore: remove jwt from RequestContext#2118
ehayes2000 merged 1 commit intomainfrom
eric/remove-jwt-from-request-context

Conversation

@ehayes2000
Copy link
Copy Markdown
Contributor

@ehayes2000 ehayes2000 commented Mar 23, 2026

remove jwt from RequestContext

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

Walkthrough

Removed the deprecated jwt field from RequestContext and eliminated passing JWT/auth parameters across many call sites. External service clients (CommsServiceClient, DocumentCognitionServiceClient) were removed in multiple modules and replaced by direct Postgres pool wiring; Scribe, DCS, Channel, Email, Document, and related client APIs were refactored to use DB-backed implementations. Several function signatures were updated to drop JWT parameters and to accept or propagate user_id where required. Example code and Cargo feature/dependency entries were updated accordingly.

Suggested reviewers

  • whutchinson98

Poem

🐇 I hopped through code with tiny feet,
JWTs tucked away, no more repeat,
Pools now hum where clients once stood,
User IDs carried — tidy and good,
A rabbit nods: simpler and sweet.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'chore: remove jwt from RequestContext' directly and clearly describes the main change across all modified files.
Description check ✅ Passed The description 'remove jwt from RequestContext' is directly related to the changeset, though minimal in detail.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch eric/remove-jwt-from-request-context

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

@ehayes2000 ehayes2000 changed the title refactor: remove jwt from RequestContext chore: remove jwt from RequestContext Mar 23, 2026
@ehayes2000 ehayes2000 force-pushed the eric/remove-jwt-from-request-context branch 5 times, most recently from bab3804 to 58c4b85 Compare March 24, 2026 22:35
@ehayes2000 ehayes2000 marked this pull request as ready for review March 25, 2026 15:20
@ehayes2000 ehayes2000 requested a review from a team as a code owner March 25, 2026 15:20
@ehayes2000 ehayes2000 force-pushed the eric/remove-jwt-from-request-context branch 2 times, most recently from d568985 to 415c770 Compare March 25, 2026 15:31
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (5)
rust/cloud-storage/document_cognition_service/src/api/context/test.rs (1)

158-161: 🧹 Nitpick | 🔵 Trivial

Unused document_cognition_service_client construction.

The document_cognition_service_client is still being constructed but is no longer passed to ScribeClient after the refactor. Consider removing this dead code.

🧹 Proposed fix to remove unused code
-    let document_cognition_service_client = Arc::new(DocumentCognitionServiceClient::new(
-        "dummy_auth_key".into(),
-        "http://localhost".into(),
-    ));

Also remove the unused import on line 115:

-    use document_cognition_service_client::DocumentCognitionServiceClient;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/document_cognition_service/src/api/context/test.rs` around
lines 158 - 161, Remove the now-dead construction of
document_cognition_service_client created via
DocumentCognitionServiceClient::new since it is no longer passed into
ScribeClient; delete the let binding for document_cognition_service_client and
also remove the corresponding unused import (the DocumentCognitionServiceClient
import) so the test file no longer contains unused code or imports.
rust/cloud-storage/memory/src/context.rs (1)

27-35: 🧹 Nitpick | 🔵 Trivial

Outdated doc comment references removed environment variables.

The doc comment still lists DOCUMENT_COGNITION_SERVICE_URL and COMMS_SERVICE_URL as required env vars, but these are no longer read after the refactor. Consider updating the documentation to reflect the actual requirements.

📝 Proposed doc comment update
 /// Builds a [`ToolServiceContext`] from environment variables and a database pool.
 ///
 /// Required env vars: `INTERNAL_API_SECRET_KEY`, `DOCUMENT_STORAGE_SERVICE_URL`,
-/// `SEARCH_SERVICE_URL`, `EMAIL_SERVICE_URL`, `SYNC_SERVICE_URL`,
-/// `DOCUMENT_COGNITION_SERVICE_URL`, `STATIC_FILE_SERVICE_URL`,
+/// `SEARCH_SERVICE_URL`, `EMAIL_SERVICE_URL`, `SYNC_SERVICE_URL`, `STATIC_FILE_SERVICE_URL`,
 /// `DOCUMENT_STORAGE_BUCKET`, `DOCX_DOCUMENT_UPLOAD_BUCKET`,
 /// `DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_DISTRIBUTION_URL`,
 /// `DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_SIGNER_PUBLIC_KEY_ID`,
 /// `DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_SIGNER_PRIVATE_KEY_SECRET_NAME`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/memory/src/context.rs` around lines 27 - 35, Update the
doc comment above the ToolServiceContext builder in context.rs to remove stale
environment variables (remove DOCUMENT_COGNITION_SERVICE_URL and
COMMS_SERVICE_URL) and ensure the listed required env vars exactly match what
the builder actually reads (e.g., INTERNAL_API_SECRET_KEY,
DOCUMENT_STORAGE_SERVICE_URL, SEARCH_SERVICE_URL, EMAIL_SERVICE_URL,
SYNC_SERVICE_URL, STATIC_FILE_SERVICE_URL, DOCUMENT_STORAGE_BUCKET,
DOCX_DOCUMENT_UPLOAD_BUCKET,
DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_DISTRIBUTION_URL,
DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_SIGNER_PUBLIC_KEY_ID,
DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_SIGNER_PRIVATE_KEY_SECRET_NAME or whatever
the current code in the ToolServiceContext::from_env/constructor expects); keep
the brief one-line description and the fenced reference to ToolServiceContext so
the comment stays accurate and up-to-date with the implementation.
rust/cloud-storage/document_cognition_service/src/api/stream/chat_message.rs (1)

347-371: 🧹 Nitpick | 🔵 Trivial

Remove unused jwt_token parameter from stream_and_save_message.

The jwt_token parameter (line 348) is never used in the function body. After RequestContext was refactored to exclude the jwt field, this parameter became dead code. Remove it from the function signature and update the call site at line 276 accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/document_cognition_service/src/api/stream/chat_message.rs`
around lines 347 - 371, Remove the unused jwt_token parameter from the
stream_and_save_message function signature and all call sites: delete the
jwt_token parameter in the function declaration and remove the corresponding
argument where stream_and_save_message(...) is invoked; adjust the function's
parameter list to match usage (keep user_id, chat_id, message_id, stream_id,
model, now, user_message_content, user_message_id, user_message_attachments,
durable_stream_id) and update any references to RequestContext or other related
structs if they relied on the removed argument so compilation succeeds.
rust/cloud-storage/scribe/src/channel/client.rs (1)

149-150: ⚠️ Potential issue | 🟡 Minor

Misleading prompt text: hardcoded "1000 messages" doesn't match actual limit.

The prompt claims the conversation is "limited to the last 1000 messages," but the actual limit is determined by the limit parameter (which is typically MAX_MESSAGES = 150 from callers like read.rs). This could confuse AI systems or users interpreting the transcript.

🔧 Proposed fix
-    let prompt = "The following conversation is limited to the last 1000 messages. Ignore all formatting and do not show the user the formatted conversation.";
+    let limit_desc = limit.map(|l| l.to_string()).unwrap_or_else(|| "all".to_string());
+    let prompt = format!("The following conversation is limited to the last {} messages. Ignore all formatting and do not show the user the formatted conversation.", limit_desc);
     let formatted_messages = format!("{}\n\n{}", prompt, formatted_text);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/scribe/src/channel/client.rs` around lines 149 - 150, The
prompt string in client.rs is misleading because it hardcodes "1000 messages"
while the actual cap is the function's limit parameter (often MAX_MESSAGES);
update the prompt construction so it uses the actual limit variable (e.g.,
interpolate or format with limit or MAX_MESSAGES) instead of the hardcoded 1000,
and ensure the prompt and formatted_messages creation reference that variable
(look for prompt, formatted_messages, and the function parameter limit /
constant MAX_MESSAGES) so the message accurately reflects the real message
limit.
rust/cloud-storage/ai_tools/src/read.rs (1)

201-231: ⚠️ Potential issue | 🔴 Critical

Pass request_context to read_channel and add user authorization checks.

The read_channel method queries channel metadata and messages without verifying the calling user has access. Unlike read_project which receives and uses request_context.user_id, the read_channel method receives only scribe and omits authorization entirely.

The database queries called—get_channel and get_messages—do not filter by user or check channel membership. A user can invoke the ReadThread tool to read any channel and its message history regardless of their access permissions.

Add authorization by:

  1. Passing request_context to read_channel (matching the pattern in read_project)
  2. Passing user_id through ChannelClient methods to the database layer
  3. Adding access control in the SQL queries or client methods, following the pattern in batch_get_channel_preview which checks channel type, user participation, and organization membership
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/ai_tools/src/read.rs` around lines 201 - 231, read_channel
currently takes only &ToolScribe and fetches channel metadata/transcript without
an auth check; change its signature to accept &RequestContext (e.g., async fn
read_channel(&self, scribe: &ToolScribe, request_context: &RequestContext) ->
...), extract the requesting user id (request_context.user_id) and pass it into
the ChannelClient calls (replace scribe.channel.get_channel_metadata(id) and
scribe.channel.get_channel_transcript(id, ...) with calls that accept a user_id
argument), and update the ChannelClient methods / SQL layer to enforce the same
access control used by batch_get_channel_preview (check channel type, user
participation or org membership) so metadata/transcript queries only return
results if the user is authorized; preserve existing error mapping to
ToolCallError.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rust/cloud-storage/scribe/src/dcs/client.rs`:
- Around line 30-47: The mapping over chat.messages is overwriting each message
timestamp with chrono::Utc::now(), which loses the original send time; inside
the closure that constructs MessageWithAttachments (in the
chat.messages.into_iter().map(...) block), use the message's original timestamp
field (e.g., m.date or whatever timestamp field exists on the message type) when
populating MessageWithAttachments.date and only fall back to chrono::Utc::now()
if that original timestamp is absent/optional; update MessageWithAttachments
population to preserve the original timestamp instead of stamping at read time.
- Around line 10-17: DcsClient lost caller-scoped authorization causing history
reads to be unprotected; restore caller identity checks by reintroducing caller
context and enforcing access checks before any chat history read. Update
DcsClient (constructor/new) to accept and store a caller identity or scope
(e.g., caller_id or AccessScope) or alternatively change the chat history
methods on DcsClient to take an explicit caller parameter, then call the
repository access check (or a new method like PgChatRepo::verify_access /
verify_thread_owner) before performing reads in methods that fetch by chat_id or
message_ids; ensure the same pattern is applied to all affected methods
referenced in the diff (the DcsClient::new area and the history-read methods at
the noted locations) so thread user_id or resolved access scope is validated and
unauthorized requests are rejected.

In `@rust/cloud-storage/scribe/src/document/project.rs`:
- Around line 62-72: In content (the async method on Project in project.rs) you
currently call get_project_content_v2 with a hard-coded AccessLevel::View;
instead first resolve the caller's real access using the existing project access
lookup (e.g. the function/logic that returns a project_user_access_level for
MacroUserIdStr), return an error if the resolved access is None/NoAccess, and
then pass that resolved AccessLevel into get_project_content_v2 so callers keep
their true Edit/Owner rights (do not hard-code AccessLevel::View). Ensure you
reference the same MacroUserIdStr and ProjectFetcher<ScribeProjectPreview>
return path and bail early on insufficient access before calling
get_project_content_v2.

---

Outside diff comments:
In `@rust/cloud-storage/ai_tools/src/read.rs`:
- Around line 201-231: read_channel currently takes only &ToolScribe and fetches
channel metadata/transcript without an auth check; change its signature to
accept &RequestContext (e.g., async fn read_channel(&self, scribe: &ToolScribe,
request_context: &RequestContext) -> ...), extract the requesting user id
(request_context.user_id) and pass it into the ChannelClient calls (replace
scribe.channel.get_channel_metadata(id) and
scribe.channel.get_channel_transcript(id, ...) with calls that accept a user_id
argument), and update the ChannelClient methods / SQL layer to enforce the same
access control used by batch_get_channel_preview (check channel type, user
participation or org membership) so metadata/transcript queries only return
results if the user is authorized; preserve existing error mapping to
ToolCallError.

In `@rust/cloud-storage/document_cognition_service/src/api/context/test.rs`:
- Around line 158-161: Remove the now-dead construction of
document_cognition_service_client created via
DocumentCognitionServiceClient::new since it is no longer passed into
ScribeClient; delete the let binding for document_cognition_service_client and
also remove the corresponding unused import (the DocumentCognitionServiceClient
import) so the test file no longer contains unused code or imports.

In
`@rust/cloud-storage/document_cognition_service/src/api/stream/chat_message.rs`:
- Around line 347-371: Remove the unused jwt_token parameter from the
stream_and_save_message function signature and all call sites: delete the
jwt_token parameter in the function declaration and remove the corresponding
argument where stream_and_save_message(...) is invoked; adjust the function's
parameter list to match usage (keep user_id, chat_id, message_id, stream_id,
model, now, user_message_content, user_message_id, user_message_attachments,
durable_stream_id) and update any references to RequestContext or other related
structs if they relied on the removed argument so compilation succeeds.

In `@rust/cloud-storage/memory/src/context.rs`:
- Around line 27-35: Update the doc comment above the ToolServiceContext builder
in context.rs to remove stale environment variables (remove
DOCUMENT_COGNITION_SERVICE_URL and COMMS_SERVICE_URL) and ensure the listed
required env vars exactly match what the builder actually reads (e.g.,
INTERNAL_API_SECRET_KEY, DOCUMENT_STORAGE_SERVICE_URL, SEARCH_SERVICE_URL,
EMAIL_SERVICE_URL, SYNC_SERVICE_URL, STATIC_FILE_SERVICE_URL,
DOCUMENT_STORAGE_BUCKET, DOCX_DOCUMENT_UPLOAD_BUCKET,
DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_DISTRIBUTION_URL,
DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_SIGNER_PUBLIC_KEY_ID,
DOCUMENT_STORAGE_SERVICE_CLOUDFRONT_SIGNER_PRIVATE_KEY_SECRET_NAME or whatever
the current code in the ToolServiceContext::from_env/constructor expects); keep
the brief one-line description and the fenced reference to ToolServiceContext so
the comment stays accurate and up-to-date with the implementation.

In `@rust/cloud-storage/scribe/src/channel/client.rs`:
- Around line 149-150: The prompt string in client.rs is misleading because it
hardcodes "1000 messages" while the actual cap is the function's limit parameter
(often MAX_MESSAGES); update the prompt construction so it uses the actual limit
variable (e.g., interpolate or format with limit or MAX_MESSAGES) instead of the
hardcoded 1000, and ensure the prompt and formatted_messages creation reference
that variable (look for prompt, formatted_messages, and the function parameter
limit / constant MAX_MESSAGES) so the message accurately reflects the real
message limit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f8a5ae79-7bd6-4ff2-a01f-8b1b945805a0

📥 Commits

Reviewing files that changed from the base of the PR and between 72924be and 58c4b85.

⛔ Files ignored due to path filters (1)
  • rust/cloud-storage/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (27)
  • rust/cloud-storage/ai/src/tool/tool_loop/cli.rs
  • rust/cloud-storage/ai_tools/src/read.rs
  • rust/cloud-storage/ai_tools/src/rewrite/tool.rs
  • rust/cloud-storage/ai_toolset/src/context.rs
  • rust/cloud-storage/chat/src/outbound/postgres/mod.rs
  • rust/cloud-storage/document_cognition_service/src/api/chats/tool/post.rs
  • rust/cloud-storage/document_cognition_service/src/api/context/test.rs
  • rust/cloud-storage/document_cognition_service/src/api/stream/chat_message.rs
  • rust/cloud-storage/document_cognition_service/src/api/stream/util/chat_message/ai_request.rs
  • rust/cloud-storage/document_cognition_service/src/bin/mcp_server.rs
  • rust/cloud-storage/document_cognition_service/src/main.rs
  • rust/cloud-storage/document_cognition_service/src/mcp_oauth/tool_service.rs
  • rust/cloud-storage/document_cognition_service/src/service/attachment/fetch.rs
  • rust/cloud-storage/document_cognition_service/src/service/get_chat.rs
  • rust/cloud-storage/documents/examples/documents_toolset.rs
  • rust/cloud-storage/memory/Cargo.toml
  • rust/cloud-storage/memory/src/context.rs
  • rust/cloud-storage/memory/src/domain/service.rs
  • rust/cloud-storage/scribe/Cargo.toml
  • rust/cloud-storage/scribe/src/channel/client.rs
  • rust/cloud-storage/scribe/src/channel/mod.rs
  • rust/cloud-storage/scribe/src/dcs/client.rs
  • rust/cloud-storage/scribe/src/dcs/mod.rs
  • rust/cloud-storage/scribe/src/document/client.rs
  • rust/cloud-storage/scribe/src/document/project.rs
  • rust/cloud-storage/scribe/src/email/client.rs
  • rust/cloud-storage/soup/examples/ai_tools_cli.rs
💤 Files with no reviewable changes (8)
  • rust/cloud-storage/documents/examples/documents_toolset.rs
  • rust/cloud-storage/document_cognition_service/src/mcp_oauth/tool_service.rs
  • rust/cloud-storage/soup/examples/ai_tools_cli.rs
  • rust/cloud-storage/memory/src/domain/service.rs
  • rust/cloud-storage/ai/src/tool/tool_loop/cli.rs
  • rust/cloud-storage/memory/Cargo.toml
  • rust/cloud-storage/document_cognition_service/src/api/chats/tool/post.rs
  • rust/cloud-storage/ai_toolset/src/context.rs

Comment on lines +30 to +47
let messages = chat
.messages
.into_iter()
.map(|m| {
let content = match m.content {
ai::types::ChatMessageContent::Text(s) => s,
other => serde_json::to_string(&other).unwrap_or_default(),
};
MessageWithAttachments {
content,
date: chrono::Utc::now(),
attachment_ids: m
.attachments
.iter()
.map(|a| a.attachment_id.clone())
.collect(),
}
})
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.

⚠️ Potential issue | 🟠 Major

Don't rewrite message timestamps at read time.

Line 40 stamps every message with chrono::Utc::now(), so the returned history no longer reflects when those messages were actually sent. Downstream consumers will see the entire thread as happening at fetch time, which breaks chronology and any recency-based behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/scribe/src/dcs/client.rs` around lines 30 - 47, The
mapping over chat.messages is overwriting each message timestamp with
chrono::Utc::now(), which loses the original send time; inside the closure that
constructs MessageWithAttachments (in the chat.messages.into_iter().map(...)
block), use the message's original timestamp field (e.g., m.date or whatever
timestamp field exists on the message type) when populating
MessageWithAttachments.date and only fall back to chrono::Utc::now() if that
original timestamp is absent/optional; update MessageWithAttachments population
to preserve the original timestamp instead of stamping at read time.

Comment on lines +62 to +72
pub async fn content(
&self,
db: &Pool<Postgres>,
user_id: MacroUserIdStr<'_>,
) -> Result<ProjectFetcher<ScribeProjectPreview>> {
let items = macro_db_client::projects::get_project::get_project_content_v2(
db,
&self.id,
user_id,
AccessLevel::View,
)
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.

⚠️ Potential issue | 🔴 Critical

Look up the caller's real project access before calling get_project_content_v2.

get_project_content_v2 trusts the project_user_access_level argument; it does not authorize or filter the project contents itself. Hard-coding AccessLevel::View here means any caller that knows a project id can read its children, and real Edit/Owner permissions are downgraded to View in the preview. Resolve the caller's actual project access first, bail if they have none, and pass that level through instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/scribe/src/document/project.rs` around lines 62 - 72, In
content (the async method on Project in project.rs) you currently call
get_project_content_v2 with a hard-coded AccessLevel::View; instead first
resolve the caller's real access using the existing project access lookup (e.g.
the function/logic that returns a project_user_access_level for MacroUserIdStr),
return an error if the resolved access is None/NoAccess, and then pass that
resolved AccessLevel into get_project_content_v2 so callers keep their true
Edit/Owner rights (do not hard-code AccessLevel::View). Ensure you reference the
same MacroUserIdStr and ProjectFetcher<ScribeProjectPreview> return path and
bail early on insufficient access before calling get_project_content_v2.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
rust/cloud-storage/memory/src/context.rs (1)

31-37: ⚠️ Potential issue | 🟡 Minor

Update the env-var docs for the new DCS wiring.

build_tool_service_context no longer reads DOCUMENT_COGNITION_SERVICE_URL, so the doc comment now overstates the required configuration. Please drop it from the list to keep setup docs accurate.

Also applies to: 88-89

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/memory/src/context.rs` around lines 31 - 37, The doc
comment listing required env vars is outdated: remove
DOCUMENT_COGNITION_SERVICE_URL from the comment for build_tool_service_context
and the duplicate mention later (the other doc comment around the same file
where the list repeats at the referenced lines) so the documented required
configuration matches the current code; locate the comment blocks that enumerate
env vars and delete the DOCUMENT_COGNITION_SERVICE_URL token from those lists
(references: build_tool_service_context).
♻️ Duplicate comments (3)
rust/cloud-storage/scribe/src/document/project.rs (1)

62-72: ⚠️ Potential issue | 🔴 Critical

Resolve the caller's actual project access before fetching content.

get_project_content_v2 trusts the project_user_access_level argument; it does not authorize or filter the project first. Hard-coding AccessLevel::View here means callers with no access can still see the children, and real Edit or Owner callers are downcast in the preview. Look up the user's actual project access, bail if none, and pass that level through.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/scribe/src/document/project.rs` around lines 62 - 72, In
the content method, don't hard-code AccessLevel::View when calling
macro_db_client::projects::get_project::get_project_content_v2; first resolve
the caller's actual project access (e.g. call the project's access lookup helper
to get the user's AccessLevel for self.id), return an error if the user has no
access, then pass that resolved AccessLevel into get_project_content_v2 so
callers with Edit/Owner keep their level and unauthorized callers are rejected.
rust/cloud-storage/scribe/src/dcs/client.rs (2)

30-47: ⚠️ Potential issue | 🟠 Major

Preserve the stored message timestamp.

Line 40 rewrites every message date to fetch time, so the returned history no longer reflects when messages were actually sent. Populate MessageWithAttachments.date from the persisted timestamp on m, and only fall back to Utc::now() if that field is genuinely absent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/scribe/src/dcs/client.rs` around lines 30 - 47, The
mapping currently sets MessageWithAttachments.date to chrono::Utc::now(), losing
the original send time; change the mapping in the chat.messages iterator so
MessageWithAttachments.date is populated from the persisted timestamp on m
(e.g., m.date or m.timestamp) and only use chrono::Utc::now() as a fallback when
that field is None/missing—update the closure that builds MessageWithAttachments
(referencing chat.messages, m, and MessageWithAttachments) to prefer the stored
timestamp and fall back to Utc::now().

10-17: ⚠️ Potential issue | 🔴 Critical

Restore caller-scoped authorization before these DB reads.

DcsClient now carries only DB/repo handles, and both history methods are keyed solely by chat_id or message_ids. With no caller/user scope in the struct or method signatures, this path cannot reject cross-user reads before hitting Postgres.

Thread user_id or an access scope through these methods and verify access before calling PgChatRepo::get_chat(...) / macro_db_client::chat_history::get_chat_history_for_messages(...).

Also applies to: 23-28, 61-65

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/scribe/src/dcs/client.rs` around lines 10 - 17, DcsClient
currently holds only DB/repo handles so its history methods can perform
cross-user reads; modify the API to accept a caller context (e.g., a user_id or
AccessScope) on construction or as an explicit parameter to the history methods
and enforce authorization before any DB call: check caller scope/ownership and
reject unauthorized callers prior to invoking PgChatRepo::get_chat(...) and
macro_db_client::chat_history::get_chat_history_for_messages(...). Update
DcsClient::new usage sites or the affected methods (the ones that call get_chat
and get_chat_history_for_messages) to propagate the caller identity and perform
the authorization check early. Ensure tests and call sites are adjusted to
supply the caller context.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rust/cloud-storage/ai_tools/src/read.rs`:
- Around line 147-153: The non-project branches (read_channel,
read_channel_message, read_chat_thread, read_chat_messages, read_email_thread,
read_email_message) currently drop the RequestContext, losing caller identity
used for authorization; restore passing the RequestContext (or at least
request_context.user_id) into each of these read_* calls so DB-side or pre-check
authorization can enforce per-user access just like read_project does (update
signatures or call-sites as needed to accept
RequestContext/request_context.user_id and propagate it through the read_*
implementations).

In `@rust/cloud-storage/ai_tools/src/rewrite/tool.rs`:
- Around line 42-47: The current rewrite path drops the caller context and calls
the internal fetch path (keyed only by markdown_file_id), enabling unauthorized
reads; restore and thread the caller's user identity into the fetch so the
user-authorized DB/service branch is used (rather than the no-JWT internal
branch that calls get_recent_document_location_v3). Concretely, in the
rewrite_markdown call flow in tool.rs (and the adjacent fetch usage around lines
referenced), propagate the user_id/caller context into the fetch invocation (or
call the user-scoped fetch variant) so the fetch function enforces per-user
authorization instead of using the bare internal fetch path. Ensure any
ToolCallError mapping remains but do not drop or replace the user context when
calling fetch.

In
`@rust/cloud-storage/document_cognition_service/src/api/stream/chat_message.rs`:
- Around line 249-250: The stream_and_save_message function is currently
capturing the raw jwt_token in spans; update its tracing/instrumentation to
avoid recording sensitive tokens by either removing the jwt_token parameter
entirely (and update all callers that pass it) or add tracing skip for that
parameter (e.g., ensure the #[tracing::instrument] on stream_and_save_message
lists jwt_token in skip(...)); apply the same change to the other affected
function/call site referenced around lines 342-349 so no bearer token is
attached to telemetry/logs.

In
`@rust/cloud-storage/document_cognition_service/src/service/attachment/fetch.rs`:
- Around line 31-37: The channel and email attachment branches currently call
the document fetch/content pipeline without the caller-scoped user_id, allowing
private transcripts to be loaded by ID; update the AttachmentType::Channel and
AttachmentType::Email branches to mirror the project branch by calling
.content(scribe.document.db(), user_id) after their fetch (e.g., where
fetch_channel(...) and fetch_email(...) are used) so the permission check is
applied; apply the same change at the other occurrences noted (around the blocks
corresponding to lines 63-67 and 99-103).

In `@rust/cloud-storage/scribe/src/channel/client.rs`:
- Around line 21-31: The get_channel_metadata path currently fetches channel
info by UUID without any caller identity check; restore per-user authorization
by accepting the caller identity (e.g., a user_id or JWT claims) into
get_channel_metadata and similar methods, validate/convert that identity (like
channel_id .try_into() is done) and perform an access check (call the existing
comms_db_client permission/membership check such as
comms_db_client::channels::is_member or an equivalent ACL method) before
returning ChannelMetadata; if the check fails return an unauthorized Error (do
the same for the other affected methods referenced at 50-62 and 67-85 so every
channel/message read path enforces the same per-user access check).

In `@rust/cloud-storage/scribe/src/email/client.rs`:
- Around line 20-22: The public methods (e.g., get_email_message_by_id and the
other methods at lines 26-35) are calling internal-only lookups
(get_message_by_id_internal / *_internal) and thus drop caller identity; replace
those calls with the user-authorized DB/service paths that enforce per-caller
authorization (e.g., call get_message_by_id or a user-scoped variant that
accepts the caller identity/context) and preserve/forward the caller principal
from the public method (self or request context) into the backend call instead
of using the internal_* helpers; do the same fix for the other listed methods so
authorization is enforced and no unconditional internal fetch is used.

---

Outside diff comments:
In `@rust/cloud-storage/memory/src/context.rs`:
- Around line 31-37: The doc comment listing required env vars is outdated:
remove DOCUMENT_COGNITION_SERVICE_URL from the comment for
build_tool_service_context and the duplicate mention later (the other doc
comment around the same file where the list repeats at the referenced lines) so
the documented required configuration matches the current code; locate the
comment blocks that enumerate env vars and delete the
DOCUMENT_COGNITION_SERVICE_URL token from those lists (references:
build_tool_service_context).

---

Duplicate comments:
In `@rust/cloud-storage/scribe/src/dcs/client.rs`:
- Around line 30-47: The mapping currently sets MessageWithAttachments.date to
chrono::Utc::now(), losing the original send time; change the mapping in the
chat.messages iterator so MessageWithAttachments.date is populated from the
persisted timestamp on m (e.g., m.date or m.timestamp) and only use
chrono::Utc::now() as a fallback when that field is None/missing—update the
closure that builds MessageWithAttachments (referencing chat.messages, m, and
MessageWithAttachments) to prefer the stored timestamp and fall back to
Utc::now().
- Around line 10-17: DcsClient currently holds only DB/repo handles so its
history methods can perform cross-user reads; modify the API to accept a caller
context (e.g., a user_id or AccessScope) on construction or as an explicit
parameter to the history methods and enforce authorization before any DB call:
check caller scope/ownership and reject unauthorized callers prior to invoking
PgChatRepo::get_chat(...) and
macro_db_client::chat_history::get_chat_history_for_messages(...). Update
DcsClient::new usage sites or the affected methods (the ones that call get_chat
and get_chat_history_for_messages) to propagate the caller identity and perform
the authorization check early. Ensure tests and call sites are adjusted to
supply the caller context.

In `@rust/cloud-storage/scribe/src/document/project.rs`:
- Around line 62-72: In the content method, don't hard-code AccessLevel::View
when calling macro_db_client::projects::get_project::get_project_content_v2;
first resolve the caller's actual project access (e.g. call the project's access
lookup helper to get the user's AccessLevel for self.id), return an error if the
user has no access, then pass that resolved AccessLevel into
get_project_content_v2 so callers with Edit/Owner keep their level and
unauthorized callers are rejected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d3698d26-d2b7-447e-86fb-5a0620eac65b

📥 Commits

Reviewing files that changed from the base of the PR and between 58c4b85 and 415c770.

⛔ Files ignored due to path filters (1)
  • rust/cloud-storage/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (27)
  • rust/cloud-storage/ai/src/tool/tool_loop/cli.rs
  • rust/cloud-storage/ai_tools/src/read.rs
  • rust/cloud-storage/ai_tools/src/rewrite/tool.rs
  • rust/cloud-storage/ai_toolset/src/context.rs
  • rust/cloud-storage/chat/src/outbound/postgres/mod.rs
  • rust/cloud-storage/document_cognition_service/src/api/chats/tool/post.rs
  • rust/cloud-storage/document_cognition_service/src/api/context/test.rs
  • rust/cloud-storage/document_cognition_service/src/api/stream/chat_message.rs
  • rust/cloud-storage/document_cognition_service/src/api/stream/util/chat_message/ai_request.rs
  • rust/cloud-storage/document_cognition_service/src/bin/mcp_server.rs
  • rust/cloud-storage/document_cognition_service/src/main.rs
  • rust/cloud-storage/document_cognition_service/src/mcp_oauth/tool_service.rs
  • rust/cloud-storage/document_cognition_service/src/service/attachment/fetch.rs
  • rust/cloud-storage/document_cognition_service/src/service/get_chat.rs
  • rust/cloud-storage/documents/examples/documents_toolset.rs
  • rust/cloud-storage/memory/Cargo.toml
  • rust/cloud-storage/memory/src/context.rs
  • rust/cloud-storage/memory/src/domain/service.rs
  • rust/cloud-storage/scribe/Cargo.toml
  • rust/cloud-storage/scribe/src/channel/client.rs
  • rust/cloud-storage/scribe/src/channel/mod.rs
  • rust/cloud-storage/scribe/src/dcs/client.rs
  • rust/cloud-storage/scribe/src/dcs/mod.rs
  • rust/cloud-storage/scribe/src/document/client.rs
  • rust/cloud-storage/scribe/src/document/project.rs
  • rust/cloud-storage/scribe/src/email/client.rs
  • rust/cloud-storage/soup/examples/ai_tools_cli.rs
💤 Files with no reviewable changes (5)
  • rust/cloud-storage/memory/src/domain/service.rs
  • rust/cloud-storage/ai_toolset/src/context.rs
  • rust/cloud-storage/memory/Cargo.toml
  • rust/cloud-storage/document_cognition_service/src/api/chats/tool/post.rs
  • rust/cloud-storage/ai/src/tool/tool_loop/cli.rs

Comment on lines +147 to 153
ContentType::Channel => self.read_channel(&scribe).await?,
ContentType::ChannelMessage => self.read_channel_message(&scribe).await?,
ContentType::ChatThread => self.read_chat_thread(&scribe).await?,
ContentType::ChatMessage => self.read_chat_messages(&scribe).await?,
ContentType::EmailThread => self.read_email_thread(&scribe).await?,
ContentType::EmailMessage => self.read_email_message(&scribe).await?,
ContentType::Project => self.read_project(&scribe, &request_context).await?,
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.

⚠️ Potential issue | 🔴 Critical

Keep caller identity on the non-project read paths.

Lines 147-152 drop RequestContext from every non-project branch, while the project path below now explicitly passes request_context.user_id for DB-side authorization. After this refactor, the tool no longer has a way to enforce per-user access on channel/chat/email reads before handing them off, which turns these branches into ID-only lookup surfaces.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/ai_tools/src/read.rs` around lines 147 - 153, The
non-project branches (read_channel, read_channel_message, read_chat_thread,
read_chat_messages, read_email_thread, read_email_message) currently drop the
RequestContext, losing caller identity used for authorization; restore passing
the RequestContext (or at least request_context.user_id) into each of these
read_* calls so DB-side or pre-check authorization can enforce per-user access
just like read_project does (update signatures or call-sites as needed to accept
RequestContext/request_context.user_id and propagate it through the read_*
implementations).

Comment on lines +42 to +47
rewrite_markdown(self.clone(), &scribe)
.await
.map_err(|err| ToolCallError {
description: "An internal error occured rewriting generating rewrite".into(),
internal_error: err,
})
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.

⚠️ Potential issue | 🔴 Critical

Restore user-scoped authorization on markdown fetch.

Dropping the caller context here and switching to fetch(...) below leaves this read path keyed only by markdown_file_id. The no-JWT branch in rust/cloud-storage/scribe/src/document/fetcher.rs:62-77 calls get_recent_document_location_v3(&document_id) with no user identity, so this tool can now read or rewrite any markdown document whose id is known. Thread user_id into a user-authorized DB/service fetch instead of the bare internal fetch path.

Also applies to: 57-59

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/ai_tools/src/rewrite/tool.rs` around lines 42 - 47, The
current rewrite path drops the caller context and calls the internal fetch path
(keyed only by markdown_file_id), enabling unauthorized reads; restore and
thread the caller's user identity into the fetch so the user-authorized
DB/service branch is used (rather than the no-JWT internal branch that calls
get_recent_document_location_v3). Concretely, in the rewrite_markdown call flow
in tool.rs (and the adjacent fetch usage around lines referenced), propagate the
user_id/caller context into the fetch invocation (or call the user-scoped fetch
variant) so the fetch function enforces per-user authorization instead of using
the bare internal fetch path. Ensure any ToolCallError mapping remains but do
not drop or replace the user context when calling fetch.

Comment on lines +249 to 250
(*user_id).clone(),
)
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.

⚠️ Potential issue | 🔴 Critical

Don't record the raw bearer token on this span.

After the JWT field was removed from RequestContext, stream_and_save_message still accepts jwt_token, and #[tracing::instrument] will capture every non-skipped parameter. That attaches the raw access token to telemetry/logs for each streamed message.

At minimum, skip jwt_token here; ideally, remove the parameter entirely if this path no longer needs it.

Minimal fix
-#[tracing::instrument(skip(ctx, request, toolset, user_message_content))]
+#[tracing::instrument(skip(ctx, request, toolset, user_message_content, jwt_token))]

Also applies to: 342-349

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/document_cognition_service/src/api/stream/chat_message.rs`
around lines 249 - 250, The stream_and_save_message function is currently
capturing the raw jwt_token in spans; update its tracing/instrumentation to
avoid recording sensitive tokens by either removing the jwt_token parameter
entirely (and update all callers that pass it) or add tracing skip for that
parameter (e.g., ensure the #[tracing::instrument] on stream_and_save_message
lists jwt_token in skip(...)); apply the same change to the other affected
function/call site referenced around lines 342-349 so no bearer token is
attached to telemetry/logs.

Comment on lines 31 to 37
AttachmentType::Project => {
// fetch id's of stuff in folder
let project_items = scribe
.document
.fetch_project(attachment.attachment_id.clone(), jwt.to_owned())
.content()
.fetch_project(attachment.attachment_id.clone())
.content(scribe.document.db(), user_id)
.await?
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.

⚠️ Potential issue | 🔴 Critical

Apply caller-scoped auth to channel/email attachments too.

Only the project branch was moved onto content(..., user_id). The channel and email branches still load content from only an attachment ID, so a private transcript or thread can be injected into model context as long as the ID is known. These fetches need the same user_id/permission check that project attachments now have.

Also applies to: 63-67, 99-103

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@rust/cloud-storage/document_cognition_service/src/service/attachment/fetch.rs`
around lines 31 - 37, The channel and email attachment branches currently call
the document fetch/content pipeline without the caller-scoped user_id, allowing
private transcripts to be loaded by ID; update the AttachmentType::Channel and
AttachmentType::Email branches to mirror the project branch by calling
.content(scribe.document.db(), user_id) after their fetch (e.g., where
fetch_channel(...) and fetch_email(...) are used) so the permission check is
applied; apply the same change at the other occurrences noted (around the blocks
corresponding to lines 63-67 and 99-103).

Comment on lines +21 to +31
#[tracing::instrument(skip(self), err)]
pub async fn get_channel_metadata(
&self,
channel_id: impl TryInto<Uuid> + Debug,
jwt_token: Option<&str>,
) -> Result<ChannelMetadata, Error> {
let channel_id = channel_id
.try_into()
.map_err(|_| anyhow::anyhow!("invalid uuid"))?;

let channel = match jwt_token {
Some(token) => self
.inner
.get_channel_metadata_external(&channel_id, token)
.await
.map_err(Error::from)?,
None => {
let channel =
comms_db_client::channels::get_channel::get_channel(&self.db, &channel_id)
.await?;

ChannelMetadataResponse {
channel_name: channel.name.unwrap_or_default(),
channel_type: channel.channel_type,
}
}
};
let channel =
comms_db_client::channels::get_channel::get_channel(&self.db, &channel_id).await?;
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.

⚠️ Potential issue | 🔴 Critical

Restore per-user authorization on channel reads.

These methods now execute raw comms_db_client lookups from only a channel/message UUID. With the old JWT parameter removed and no user_id or access check replacing it, callers can read private channels and DMs by ID alone. Thread caller identity into this API and reject unauthorized reads before returning metadata or formatted content.

Also applies to: 50-62, 67-85

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/scribe/src/channel/client.rs` around lines 21 - 31, The
get_channel_metadata path currently fetches channel info by UUID without any
caller identity check; restore per-user authorization by accepting the caller
identity (e.g., a user_id or JWT claims) into get_channel_metadata and similar
methods, validate/convert that identity (like channel_id .try_into() is done)
and perform an access check (call the existing comms_db_client
permission/membership check such as comms_db_client::channels::is_member or an
equivalent ACL method) before returning ChannelMetadata; if the check fails
return an unauthorized Error (do the same for the other affected methods
referenced at 50-62 and 67-85 so every channel/message read path enforces the
same per-user access check).

Comment on lines +20 to +22
#[tracing::instrument(skip(self), err)]
pub async fn get_email_message_by_id(&self, message_id: &str) -> Result<ParsedMessage> {
self.inner.get_message_by_id_internal(message_id).await
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.

⚠️ Potential issue | 🔴 Critical

Don't replace user-authenticated email reads with bare internal ID lookups.

These methods now drop the caller identity and always hit *_internal(...) using only message_id or thread_id. That removes the information needed for a caller-specific authorization check, so any internal path that learns an id can read mail outside the current user. Keep a user-authorized DB/service path here instead of unconditional internal fetches.

Also applies to: 26-35

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/scribe/src/email/client.rs` around lines 20 - 22, The
public methods (e.g., get_email_message_by_id and the other methods at lines
26-35) are calling internal-only lookups (get_message_by_id_internal /
*_internal) and thus drop caller identity; replace those calls with the
user-authorized DB/service paths that enforce per-caller authorization (e.g.,
call get_message_by_id or a user-scoped variant that accepts the caller
identity/context) and preserve/forward the caller principal from the public
method (self or request context) into the backend call instead of using the
internal_* helpers; do the same fix for the other listed methods so
authorization is enforced and no unconditional internal fetch is used.

Remove the deprecated jwt field from RequestContext by refactoring all
tools and scribe clients to use direct DB access instead of JWT-authenticated
HTTP calls.

Scribe changes:
- DcsClient: uses chat hex crate (PgChatRepo) for get_chat_history,
  macro_db_client for get_chat_history_for_messages
- ChannelClient: removed CommsServiceClient dependency entirely, all
  methods use direct comms_db_client queries. Removed list_channels
  (dead code), removed JWT Option params from metadata/transcript.
- EmailClient: removed JWT Option params and external HTTP branches,
  all methods use internal service endpoints only. Removed batch method
  (dead code).
- DocumentClient: fetch_project uses macro_db_client directly
- Removed comms_service_client and document_cognition_service_client
  dependencies from scribe

Tool changes:
- ReadThread: all methods use internal (non-JWT) scribe paths
- MarkdownRewrite: uses fetch() instead of fetch_with_auth()
- ai_toolset RequestContext: removed jwt field

DCS changes:
- Removed unused DocumentCognitionServiceClient from scribe wiring
- Threaded user_id through fetchium for project fetching
- Updated all callers for simplified scribe APIs
@ehayes2000 ehayes2000 force-pushed the eric/remove-jwt-from-request-context branch from 415c770 to 1696d23 Compare March 25, 2026 16:19
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
rust/cloud-storage/document_cognition_service/src/main.rs (1)

98-98: ⚠️ Potential issue | 🔴 Critical

Remove unused comms_service_client field from ApiContext and its initialization.

Search of the codebase confirms comms_service_client is never accessed anywhere. The field is defined in ApiContext (api/context/mod.rs:38), constructed in main.rs line 98, but has zero usages in production code. This is dead code that should be removed from the struct definition and its initialization in main.rs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/document_cognition_service/src/main.rs` at line 98, Remove
the unused comms_service_client field by deleting its declaration from the
ApiContext struct (symbol: comms_service_client) and removing its initialization
where CommsServiceClient::new(...) is called in main (symbol:
CommsServiceClient::new and the local variable comms_service_client). Update any
ApiContext construction sites (e.g., where ApiContext::new or struct literal is
created) to stop supplying comms_service_client and run cargo build to ensure no
remaining references exist; if any tests or mocks reference
comms_service_client, remove or adapt them accordingly.
rust/cloud-storage/document_cognition_service/src/api/stream/util/chat_message/ai_request.rs (1)

14-23: ⚠️ Potential issue | 🟡 Minor

Add explicit user_id field specification to match codebase patterns.

The user_id parameter will be auto-captured by tracing::instrument and logged via its Debug impl. The codebase pattern is to explicitly specify user IDs in the fields() attribute rather than skipping them (see similar patterns in chat_history_batch_messages.rs, search_service, and properties_service).

🔧 Recommended fix
-#[tracing::instrument(skip(ctx, chat, incoming_message, static_system_prompt, jwt), err)]
+#[tracing::instrument(
+    skip(ctx, chat, incoming_message, static_system_prompt, jwt),
+    fields(user_id = %user_id),
+    err
+)]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@rust/cloud-storage/document_cognition_service/src/api/stream/util/chat_message/ai_request.rs`
around lines 14 - 23, Update the tracing attribute on
build_chat_completion_request to explicitly include the user_id field in
fields(), e.g. change #[tracing::instrument(skip(ctx, chat, incoming_message,
static_system_prompt, jwt), err)] to include fields(user_id = ?user_id) (or use
%user_id if Display is preferred) so the user id is logged consistently with the
project's pattern; ensure the skip list still contains the same parameters.
♻️ Duplicate comments (8)
rust/cloud-storage/scribe/src/document/project.rs (1)

62-79: ⚠️ Potential issue | 🔴 Critical

Authorization bypass: hard-coded AccessLevel::View skips actual permission verification.

The past review correctly identified this issue. Per the context snippet, get_project_content_v2 trusts the passed project_user_access_level without verifying the user's actual access. Hard-coding AccessLevel::View means:

  1. Any user with any project ID can read its contents
  2. Users with Edit/Owner permissions are incorrectly downgraded to View in the response

Resolve the caller's actual project access level first (bail if none), then pass that level through.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/scribe/src/document/project.rs` around lines 62 - 79, The
content method in ProjectFetcher currently hard-codes AccessLevel::View when
calling macro_db_client::projects::get_project::get_project_content_v2, causing
authorization bypass and downgrading higher-permission users; change it to first
resolve the caller's actual access level (e.g., call the DB/ACL helper to fetch
the user's project access for self.id and user_id, return an error if no
access), then pass that resolved AccessLevel into get_project_content_v2 so the
returned ScribeProjectPreview and ProjectFetcher respect the real permissions.
rust/cloud-storage/document_cognition_service/src/api/stream/chat_message.rs (1)

342-348: ⚠️ Potential issue | 🟠 Major

Sensitive token logged: jwt_token is captured in tracing spans.

The #[tracing::instrument] at line 342 skips ctx, request, toolset, and user_message_content, but jwt_token (line 348) is still captured. This attaches the raw bearer token to telemetry/logs for every streamed message.

Add jwt_token to the skip list:

-#[tracing::instrument(skip(ctx, request, toolset, user_message_content))]
+#[tracing::instrument(skip(ctx, request, toolset, user_message_content, jwt_token))]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/document_cognition_service/src/api/stream/chat_message.rs`
around lines 342 - 348, The tracing span for stream_and_save_message currently
captures the raw jwt_token (sensitive) because it isn't skipped; update the
#[tracing::instrument(...)] attribute on stream_and_save_message to include
jwt_token in the skip list so the bearer token is not attached to spans/logs,
ensuring the attribute lists ctx, request, toolset, user_message_content, and
jwt_token.
rust/cloud-storage/scribe/src/email/client.rs (1)

20-35: ⚠️ Potential issue | 🔴 Critical

Authorization bypass: internal lookups skip caller permission checks.

The past review correctly flagged this concern. Both methods now unconditionally call *_internal variants, which bypass user authorization. Any caller knowing a message_id or thread_id can read emails regardless of their actual access rights.

Either:

  1. Restore user-authorized paths that verify the caller has access to the email/thread, or
  2. Ensure all callers of EmailClient have already performed authorization checks upstream (and document this contract).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/scribe/src/email/client.rs` around lines 20 - 35, The
methods get_email_message_by_id and get_email_messages_by_thread_id currently
call the internal helpers (get_message_by_id_internal and
get_messages_by_thread_id_internal) which bypass authorization; update these
methods to perform or delegate an explicit authorization check before returning
data — either call the public/non-internal variants that enforce access control
or add a permission check (e.g., verify caller identity/ownership/scopes) and
only call the *_internal functions after authorization passes; alternatively, if
you decide to keep internal calls, document and enforce that all EmailClient
callers must authorize upstream and add assertions/logging in
get_email_message_by_id and get_email_messages_by_thread_id to guarantee the
contract.
rust/cloud-storage/ai_tools/src/read.rs (1)

146-152: ⚠️ Potential issue | 🔴 Critical

Propagate caller identity through the non-project read paths.

read_project still passes request_context.user_id into the DB-backed fetch, but the channel/chat/email branches now drop RequestContext entirely. With the Scribe clients now reading from Postgres directly, these helpers become ID-only lookups unless you thread user_id back through the helper signatures and underlying Scribe calls.

Also applies to: 201-376

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/ai_tools/src/read.rs` around lines 146 - 152, The
non-project branches of the match
(ContentType::Channel/ChannelMessage/ChatThread/ChatMessage/EmailThread/EmailMessage)
drop RequestContext and thus lose caller identity; update the helper signatures
(read_channel, read_channel_message, read_chat_thread, read_chat_messages,
read_email_thread, read_email_message) to accept the caller user_id (or
RequestContext) and propagate that value into the Scribe client calls and any
DB-backed fetches they perform, mirroring how read_project passes
request_context.user_id, and adjust all call sites in read.rs (and the
underlying scribe methods) so the DB lookups become identity-aware rather than
ID-only.
rust/cloud-storage/document_cognition_service/src/service/attachment/fetch.rs (1)

16-21: ⚠️ Potential issue | 🔴 Critical

Apply the new user_id to every attachment read, not just projects.

This change threads user_id through fetchium, but only the project branch on Lines 35-36 consumes it. Channel and email attachments are still fetched by ID alone, so the caller-scoped protection added here remains incomplete for private transcripts and threads. Thread user_id into those client APIs as well.

Also applies to: 24-29, 31-37, 139-145

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@rust/cloud-storage/document_cognition_service/src/service/attachment/fetch.rs`
around lines 16 - 21, fetchium now accepts user_id but only applies it for
project attachments; update the channel and email attachment fetch paths to pass
the same user_id to their client APIs so caller-scoped protection is applied
uniformly. Locate the fetchium function and the branches handling
ChatAttachmentWithName variants (project, channel, email) and modify the
channel- and email-related calls to include the user_id argument (the same
MacroUserIdStr<'static> passed into fetchium) when invoking the underlying
client methods that load attachments, ensuring all Attachment fetches use the
user_id guard like the project branch does.
rust/cloud-storage/scribe/src/dcs/client.rs (1)

30-47: ⚠️ Potential issue | 🟠 Major

Preserve persisted message timestamps instead of synthesizing them.

Line 40 stamps every entry with chrono::Utc::now(), so get_chat_history() no longer reflects when messages were actually sent. The get_chat_history_for_messages() path below already uses a timestamp-preserving query; this method needs the same kind of source/model instead of reconstructing history with fetch-time dates.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/scribe/src/dcs/client.rs` around lines 30 - 47, The
mapping currently overwrites original message times by setting
MessageWithAttachments.date = chrono::Utc::now(); instead, read and preserve the
persisted timestamp from the incoming message struct (use the message's existing
timestamp/date field on `m`—e.g., `m.date`/`m.timestamp`/`m.sent_at` as
appropriate) and assign that to `MessageWithAttachments.date` so this path
mirrors the timestamp-preserving behavior used by
`get_chat_history_for_messages()`.
rust/cloud-storage/scribe/src/channel/client.rs (1)

15-18: ⚠️ Potential issue | 🔴 Critical

Restore caller-scoped authorization before these DB reads.

The constructor now keeps only a raw DB pool, and every public read method here fetches by channel/message UUID alone. That means metadata, transcripts, and surrounding message context for private channels and DMs can be returned without any membership check once an ID is known. Thread caller identity into this client and verify access before calling the DB helpers.

Also applies to: 21-31, 50-62, 67-85

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/scribe/src/channel/client.rs` around lines 15 - 18, The
ChannelClient currently only stores a raw DB pool (ChannelClient::new) which
allows public read methods to fetch channel/message data by UUID without
authorization; change the constructor and struct to accept and store a caller
identity/context (e.g., a Caller or UserId) and thread that identity into every
public read method (e.g., the methods that fetch channel metadata, messages,
message context, transcript) so each method performs an access check against the
DB (membership/DM ownership/visibility) before calling the DB helpers; implement
and call a helper like ensure_member_or_visible(channel_id, caller) or
ensure_caller_can_view_message(message_id, caller) and return an authorization
error when the check fails.
rust/cloud-storage/ai_tools/src/rewrite/tool.rs (1)

42-47: ⚠️ Potential issue | 🔴 Critical

Reintroduce caller-scoped document fetch here.

Lines 42-47 drop the caller context, and Line 57 switches to scribe.document.fetch(...). That path builds a no-token fetcher and falls back to internal DSL reads by document ID, so the rewrite tool can now load any known markdown file without the caller’s authorization context. Use a user-scoped fetch path here instead of the bare internal fetch.

Also applies to: 55-59

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/ai_tools/src/rewrite/tool.rs` around lines 42 - 47, The
rewrite tool is currently invoking rewrite_markdown(self.clone(), &scribe) which
allows scribe.document.fetch to run as a no-token internal fetch; change the
flow so the document is loaded with the caller's authorization context instead
of the internal fetch. Concretely, ensure you either (A) perform a caller-scoped
fetch before calling rewrite_markdown (use the caller-aware variant of
scribe.document.fetch or the helper that accepts the caller token) and pass the
fetched document or caller-scoped scribe into rewrite_markdown, or (B) extend
rewrite_markdown to accept the caller context and use that when invoking
scribe.document.fetch; apply the same fix to the other occurrence around lines
55-59 so both paths use caller-scoped fetching rather than the bare internal
fetch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@rust/cloud-storage/document_cognition_service/src/api/stream/util/chat_message/ai_request.rs`:
- Around line 14-23: Update the tracing attribute on
build_chat_completion_request to explicitly include the user_id field in
fields(), e.g. change #[tracing::instrument(skip(ctx, chat, incoming_message,
static_system_prompt, jwt), err)] to include fields(user_id = ?user_id) (or use
%user_id if Display is preferred) so the user id is logged consistently with the
project's pattern; ensure the skip list still contains the same parameters.

In `@rust/cloud-storage/document_cognition_service/src/main.rs`:
- Line 98: Remove the unused comms_service_client field by deleting its
declaration from the ApiContext struct (symbol: comms_service_client) and
removing its initialization where CommsServiceClient::new(...) is called in main
(symbol: CommsServiceClient::new and the local variable comms_service_client).
Update any ApiContext construction sites (e.g., where ApiContext::new or struct
literal is created) to stop supplying comms_service_client and run cargo build
to ensure no remaining references exist; if any tests or mocks reference
comms_service_client, remove or adapt them accordingly.

---

Duplicate comments:
In `@rust/cloud-storage/ai_tools/src/read.rs`:
- Around line 146-152: The non-project branches of the match
(ContentType::Channel/ChannelMessage/ChatThread/ChatMessage/EmailThread/EmailMessage)
drop RequestContext and thus lose caller identity; update the helper signatures
(read_channel, read_channel_message, read_chat_thread, read_chat_messages,
read_email_thread, read_email_message) to accept the caller user_id (or
RequestContext) and propagate that value into the Scribe client calls and any
DB-backed fetches they perform, mirroring how read_project passes
request_context.user_id, and adjust all call sites in read.rs (and the
underlying scribe methods) so the DB lookups become identity-aware rather than
ID-only.

In `@rust/cloud-storage/ai_tools/src/rewrite/tool.rs`:
- Around line 42-47: The rewrite tool is currently invoking
rewrite_markdown(self.clone(), &scribe) which allows scribe.document.fetch to
run as a no-token internal fetch; change the flow so the document is loaded with
the caller's authorization context instead of the internal fetch. Concretely,
ensure you either (A) perform a caller-scoped fetch before calling
rewrite_markdown (use the caller-aware variant of scribe.document.fetch or the
helper that accepts the caller token) and pass the fetched document or
caller-scoped scribe into rewrite_markdown, or (B) extend rewrite_markdown to
accept the caller context and use that when invoking scribe.document.fetch;
apply the same fix to the other occurrence around lines 55-59 so both paths use
caller-scoped fetching rather than the bare internal fetch.

In
`@rust/cloud-storage/document_cognition_service/src/api/stream/chat_message.rs`:
- Around line 342-348: The tracing span for stream_and_save_message currently
captures the raw jwt_token (sensitive) because it isn't skipped; update the
#[tracing::instrument(...)] attribute on stream_and_save_message to include
jwt_token in the skip list so the bearer token is not attached to spans/logs,
ensuring the attribute lists ctx, request, toolset, user_message_content, and
jwt_token.

In
`@rust/cloud-storage/document_cognition_service/src/service/attachment/fetch.rs`:
- Around line 16-21: fetchium now accepts user_id but only applies it for
project attachments; update the channel and email attachment fetch paths to pass
the same user_id to their client APIs so caller-scoped protection is applied
uniformly. Locate the fetchium function and the branches handling
ChatAttachmentWithName variants (project, channel, email) and modify the
channel- and email-related calls to include the user_id argument (the same
MacroUserIdStr<'static> passed into fetchium) when invoking the underlying
client methods that load attachments, ensuring all Attachment fetches use the
user_id guard like the project branch does.

In `@rust/cloud-storage/scribe/src/channel/client.rs`:
- Around line 15-18: The ChannelClient currently only stores a raw DB pool
(ChannelClient::new) which allows public read methods to fetch channel/message
data by UUID without authorization; change the constructor and struct to accept
and store a caller identity/context (e.g., a Caller or UserId) and thread that
identity into every public read method (e.g., the methods that fetch channel
metadata, messages, message context, transcript) so each method performs an
access check against the DB (membership/DM ownership/visibility) before calling
the DB helpers; implement and call a helper like
ensure_member_or_visible(channel_id, caller) or
ensure_caller_can_view_message(message_id, caller) and return an authorization
error when the check fails.

In `@rust/cloud-storage/scribe/src/dcs/client.rs`:
- Around line 30-47: The mapping currently overwrites original message times by
setting MessageWithAttachments.date = chrono::Utc::now(); instead, read and
preserve the persisted timestamp from the incoming message struct (use the
message's existing timestamp/date field on `m`—e.g.,
`m.date`/`m.timestamp`/`m.sent_at` as appropriate) and assign that to
`MessageWithAttachments.date` so this path mirrors the timestamp-preserving
behavior used by `get_chat_history_for_messages()`.

In `@rust/cloud-storage/scribe/src/document/project.rs`:
- Around line 62-79: The content method in ProjectFetcher currently hard-codes
AccessLevel::View when calling
macro_db_client::projects::get_project::get_project_content_v2, causing
authorization bypass and downgrading higher-permission users; change it to first
resolve the caller's actual access level (e.g., call the DB/ACL helper to fetch
the user's project access for self.id and user_id, return an error if no
access), then pass that resolved AccessLevel into get_project_content_v2 so the
returned ScribeProjectPreview and ProjectFetcher respect the real permissions.

In `@rust/cloud-storage/scribe/src/email/client.rs`:
- Around line 20-35: The methods get_email_message_by_id and
get_email_messages_by_thread_id currently call the internal helpers
(get_message_by_id_internal and get_messages_by_thread_id_internal) which bypass
authorization; update these methods to perform or delegate an explicit
authorization check before returning data — either call the public/non-internal
variants that enforce access control or add a permission check (e.g., verify
caller identity/ownership/scopes) and only call the *_internal functions after
authorization passes; alternatively, if you decide to keep internal calls,
document and enforce that all EmailClient callers must authorize upstream and
add assertions/logging in get_email_message_by_id and
get_email_messages_by_thread_id to guarantee the contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9bfe70f7-3d1d-43e6-9b54-6e69769361cd

📥 Commits

Reviewing files that changed from the base of the PR and between 415c770 and 1696d23.

⛔ Files ignored due to path filters (1)
  • rust/cloud-storage/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • rust/cloud-storage/ai/src/tool/tool_loop/cli.rs
  • rust/cloud-storage/ai_tools/src/read.rs
  • rust/cloud-storage/ai_tools/src/rewrite/tool.rs
  • rust/cloud-storage/ai_toolset/src/context.rs
  • rust/cloud-storage/chat/src/outbound/postgres/mod.rs
  • rust/cloud-storage/document_cognition_service/src/api/chats/tool/post.rs
  • rust/cloud-storage/document_cognition_service/src/api/context/test.rs
  • rust/cloud-storage/document_cognition_service/src/api/stream/chat_message.rs
  • rust/cloud-storage/document_cognition_service/src/api/stream/util/chat_message/ai_request.rs
  • rust/cloud-storage/document_cognition_service/src/bin/mcp_server.rs
  • rust/cloud-storage/document_cognition_service/src/main.rs
  • rust/cloud-storage/document_cognition_service/src/mcp_oauth/tool_service.rs
  • rust/cloud-storage/document_cognition_service/src/service/attachment/fetch.rs
  • rust/cloud-storage/document_cognition_service/src/service/get_chat.rs
  • rust/cloud-storage/documents/examples/documents_toolset.rs
  • rust/cloud-storage/email/examples/ai_tools_cli.rs
  • rust/cloud-storage/memory/Cargo.toml
  • rust/cloud-storage/memory/src/context.rs
  • rust/cloud-storage/memory/src/domain/service.rs
  • rust/cloud-storage/scribe/Cargo.toml
  • rust/cloud-storage/scribe/src/channel/client.rs
  • rust/cloud-storage/scribe/src/channel/mod.rs
  • rust/cloud-storage/scribe/src/dcs/client.rs
  • rust/cloud-storage/scribe/src/dcs/mod.rs
  • rust/cloud-storage/scribe/src/document/client.rs
  • rust/cloud-storage/scribe/src/document/project.rs
  • rust/cloud-storage/scribe/src/email/client.rs
  • rust/cloud-storage/soup/examples/ai_tools_cli.rs
💤 Files with no reviewable changes (5)
  • rust/cloud-storage/memory/Cargo.toml
  • rust/cloud-storage/ai_toolset/src/context.rs
  • rust/cloud-storage/ai/src/tool/tool_loop/cli.rs
  • rust/cloud-storage/document_cognition_service/src/api/chats/tool/post.rs
  • rust/cloud-storage/memory/src/domain/service.rs

@ehayes2000 ehayes2000 merged commit 2078094 into main Mar 25, 2026
41 checks passed
@ehayes2000 ehayes2000 deleted the eric/remove-jwt-from-request-context branch March 25, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant