Skip to content

Dev - #8

Merged
zlovtnik merged 8 commits into
mainfrom
dev
Jul 3, 2026
Merged

Dev#8
zlovtnik merged 8 commits into
mainfrom
dev

Conversation

@zlovtnik

@zlovtnik zlovtnik commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added a bundled web UI with breadcrumbs, dynamic page titles, target-aware navigation, and improved table row selection.
    • Introduced persisted SQL-file management (status, listing, upload via zip, copy hashes, and clearing).
    • Added Mongo-backed persistence and enhanced drift output with schema control summary/details.
  • Bug Fixes
    • Improved PostgreSQL URL normalization/validation, including credential handling and error redaction.
    • Enhanced drift interactions (expandable details, filters, and stable copy/status behaviors).
  • Documentation
    • Updated local development, Docker Compose, commands, and environment-variable/CLI guidance.
  • Tests
    • Expanded UI and backend test coverage for new flows and edge cases.

zlovtnik added 5 commits June 30, 2026 20:07
- Add MongoDB driver dependency and configuration for persisting database targets.
- Introduce BEDROCK_MONGO_URI, BEDROCK_MONGO_DATABASE, BEDROCK_MONGO_TARGETS_COLLECTION environment variables.
- Add BEDROCK_API_BEARER_TOKEN for HTTP API authentication.
- Add BEDROCK_DB_TEST_ALLOWED_HOSTS to restrict hosts for connection tests.
- Update docker-compose with MongoDB service and required environment variables.
- Update README with new CLI option and environment variables.
- Add react-helmet-async dependency for managing page titles
- Implement AppBreadcrumbs component and active route title extraction
- Update AppShell to display breadcrumbs and set document title based on current route
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 58ef7f3d-e764-465f-877d-5b65be2c908a

📥 Commits

Reviewing files that changed from the base of the PR and between 820a7c9 and 4a10e99.

📒 Files selected for processing (10)
  • .gitignore
  • README.md
  • docker-compose.yml
  • nginx/nginx.conf.template
  • schema-migrator-ui/src/pages/Drift/DriftPage.test.tsx
  • schema-migrator-ui/src/pages/Drift/DriftPage.tsx
  • schema-migrator-ui/src/styles.css
  • src/main/scala/com/sslproxy/schema/server/PostgresDriftAnalyzer.scala
  • src/main/scala/com/sslproxy/schema/server/SchemaRoutes.scala
  • src/test/scala/com/sslproxy/schema/server/PostgresDriftAnalyzerSuite.scala

📝 Walkthrough

Walkthrough

This PR adds Mongo-backed persistence for targets and SQL files, static API bearer-token auth, Postgres drift analysis with control summaries, a SQL-files management UI, breadcrumb/title routing metadata, and supporting docs, Docker, and build updates.

Changes

Backend: persistence, auth, drift, and SQL-file routes

Layer / File(s) Summary
Config, Mongo stores, and CLI wiring
src/main/scala/com/sslproxy/schema/config/MigratorConfig.scala, src/main/scala/com/sslproxy/schema/cli/CliOpts.scala, src/main/scala/com/sslproxy/schema/store/*, src/test/scala/com/sslproxy/schema/config/ServerConfigSuite.scala, src/test/scala/com/sslproxy/schema/store/TargetStoreSuite.scala
Adds Mongo/API-bearer config, Mongo-backed target and SQL-file stores, CLI composition, and validation tests.
HTTP server, auth, and route wiring
src/main/scala/com/sslproxy/schema/server/*, src/main/scala/com/sslproxy/schema/db/*, src/main/scala/com/sslproxy/schema/engine/*, src/main/scala/com/sslproxy/schema/validation/*, src/test/scala/com/sslproxy/schema/db/*, src/test/scala/com/sslproxy/schema/server/auth/*, src/test/scala/com/sslproxy/schema/server/compress/*
Threads SQL-file storage through server routes, normalizes Postgres URLs, adds static bearer auth, refactors SQL reading, and updates middleware/connection handling.
SQL-file API and upload normalization
src/main/scala/com/sslproxy/schema/server/SqlFileRoutes.scala, src/main/scala/com/sslproxy/schema/discovery/SqlFile.scala, src/main/scala/com/sslproxy/schema/discovery/DiscoveryService.scala, src/test/scala/com/sslproxy/schema/discovery/DiscoveryServiceSuite.scala, src/test/scala/com/sslproxy/schema/server/RoutesSuite.scala
Adds SQL-file upload/list/clear endpoints, path normalization, zip extraction limits, and discovery-from-files support.
Postgres drift analyzer and schema control summaries
src/main/scala/com/sslproxy/schema/server/PostgresDriftAnalyzer.scala, src/main/scala/com/sslproxy/schema/server/SchemaRoutes.scala, src/main/scala/com/sslproxy/schema/store/Models.scala, src/main/scala/com/sslproxy/schema/types/index.ts, src/test/scala/com/sslproxy/schema/server/PostgresDriftAnalyzerSuite.scala, src/test/scala/com/sslproxy/schema/server/RoutesSuite.scala
Adds DDL parsing/comparison, control summaries, drift classification, and the schema/drift response wiring.

Frontend: selected target, SQL files page, drift, schema, breadcrumbs

Layer / File(s) Summary
Selected-target provider and consumers
schema-migrator-ui/src/hooks/useSelectedTarget.tsx, schema-migrator-ui/src/components/TargetSelector.tsx, schema-migrator-ui/src/pages/*, schema-migrator-ui/src/main.tsx, schema-migrator-ui/src/test/render.tsx, schema-migrator-ui/src/test/setup.ts, schema-migrator-ui/src/hooks/useSelectedTarget.test.tsx
Adds a persisted selected-target context/hook and replaces direct URL-param reads across the app.
SQL files page and client
schema-migrator-ui/src/api/sqlFiles.ts, schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.tsx, schema-migrator-ui/src/router.tsx, schema-migrator-ui/src/styles.css, schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.test.tsx
Adds the SQL-files page, zip upload flow, route metadata, and supporting styles/tests.
Breadcrumbs, document title, and router metadata
schema-migrator-ui/src/components/breadcrumbs.ts, schema-migrator-ui/src/components/AppBreadcrumbs.tsx, schema-migrator-ui/src/components/titleFormatting.ts, schema-migrator-ui/src/components/DocumentTitle.tsx, schema-migrator-ui/src/layouts/AppShell.tsx, schema-migrator-ui/src/router.tsx
Derives breadcrumb/title metadata from route handles and renders them in the app shell.
Drift page control summary and row selection
schema-migrator-ui/src/pages/Drift/DriftPage.tsx, schema-migrator-ui/src/pages/Drift/DriftPage.test.tsx, schema-migrator-ui/src/types/index.ts, schema-migrator-ui/src/components/ui/DataTable.tsx, schema-migrator-ui/src/styles.css
Adds drift filtering, control summary, selected rows, and expandable drift detail UI.
Schema page checksum copy and tree icons
schema-migrator-ui/src/pages/Schema/SchemaPage.tsx, schema-migrator-ui/src/pages/Schema/SchemaPage.test.tsx, schema-migrator-ui/src/components/ui/DataTable.tsx, schema-migrator-ui/src/types/index.ts, schema-migrator-ui/src/components/ConnectionForm.tsx, schema-migrator-ui/src/pages/Targets/TargetListPage.tsx, schema-migrator-ui/src/styles.css
Adds checksum copy actions, selection styling, schema-tree icons, and Database URL labeling.

Docs, Docker, nginx, and build config

Layer / File(s) Summary
Docs and runtime wiring
AGENTS.md, README.md, docker-compose.yml, nginx/Dockerfile, nginx/nginx.conf.template, build.sbt, .gitignore, schema-migrator-ui/package.json
Updates repository docs, Docker Compose Mongo wiring, nginx bearer-token forwarding, and the Mongo/helmet dependencies.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SqlFileRoutes
  participant SqlFileStore
  participant SchemaRoutes
  participant PostgresDriftAnalyzer

  Client->>SqlFileRoutes: POST /sql-files/upload-zip
  SqlFileRoutes->>SqlFileRoutes: extract and normalize .sql entries
  SqlFileRoutes->>SqlFileStore: replaceAll(files)
  SqlFileStore-->>SqlFileRoutes: ack
  Client->>SchemaRoutes: GET /drift
  SchemaRoutes->>SqlFileStore: toSqlFiles()
  SqlFileStore-->>SchemaRoutes: List[SqlFile]
  SchemaRoutes->>PostgresDriftAnalyzer: mergeCatalog, driftItems
  PostgresDriftAnalyzer-->>SchemaRoutes: DriftResponse data
Loading
sequenceDiagram
  participant Client
  participant JwtMiddleware
  participant JwtTokens

  Client->>JwtMiddleware: request with Authorization header
  JwtMiddleware->>JwtMiddleware: constantTimeEquals(token, apiBearerToken)
  alt static token matches
    JwtMiddleware-->>Client: authorized
  else
    JwtMiddleware->>JwtTokens: verify(jwtSecret, token)
    JwtTokens-->>JwtMiddleware: Claims or error
  end
Loading

Possibly related PRs

  • zlovtnik/schema-migrator#5: Both PRs touch the same HTTP/auth/middleware and route wiring paths, especially HttpServer, Routes, and JwtMiddleware.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is too vague to convey the main change; it doesn't describe the feature or scope of this PR. Replace it with a concise, specific title summarizing the primary change, such as the new SQL file and target persistence work.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://github.com/@api/repos/zlovtnik/schema-migrator/issues/comments/4861640325","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- review_stack_entry_start -->\n\n[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/zlovtnik/schema-migrator/pull/8?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)\n\n<!-- review_stack_entry_end -->\n<!-- This is an auto-generated comment: review in progress by coderabbit.ai -->\n\n> [!NOTE]\n> Currently processing new changes in this PR. This may take a few minutes, please wait...\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: defaults\n> \n> **Review profile**: CHILL\n> \n> **Plan**: Pro\n> \n> **Run ID**: `a2803d98-7785-4155-9c0c-b25094d1df3f`\n> \n> </details>\n> \n> <details>\n> <summary>📥 Commits</summary>\n> \n> Reviewing files that changed from the base of the PR and between b1c1dba2dc8b11b390321c3f829821de6fe3f8e2 and b81108aaf646da94df4c17d15a7726b03848bb1c.\n> \n> </details>\n> \n> <details>\n> <summary>⛔ Files ignored due to path filters (2)</summary>\n> \n> * `schema-migrator-ui/bun.lock` is excluded by `!**/*.lock`\n> * `schema-migrator-ui/pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml`\n> \n> </details>\n> \n> <details>\n> <summary>📒 Files selected for processing (71)</summary>\n> \n> * `.bsp/sbt.json`\n> * `AGENTS.md`\n> * `README.md`\n> * `build.sbt`\n> * `docker-compose.yml`\n> * `nginx/Dockerfile`\n> * `nginx/nginx.conf.template`\n> * `schema-migrator-ui/package.json`\n> * `schema-migrator-ui/src/api/sqlFiles.ts`\n> * `schema-migrator-ui/src/components/AppBreadcrumbs.test.ts`\n> * `schema-migrator-ui/src/components/AppBreadcrumbs.tsx`\n> * `schema-migrator-ui/src/components/ConnectionForm.tsx`\n> * `schema-migrator-ui/src/components/DocumentTitle.test.ts`\n> * `schema-migrator-ui/src/components/DocumentTitle.tsx`\n> * `schema-migrator-ui/src/components/TargetSelector.tsx`\n> * `schema-migrator-ui/src/components/breadcrumbs.ts`\n> * `schema-migrator-ui/src/components/titleFormatting.ts`\n> * `schema-migrator-ui/src/components/ui/DataTable.tsx`\n> * `schema-migrator-ui/src/hooks/useSelectedTarget.tsx`\n> * `schema-migrator-ui/src/layouts/AppShell.tsx`\n> * `schema-migrator-ui/src/main.tsx`\n> * `schema-migrator-ui/src/pages/Drift/DriftPage.test.tsx`\n> * `schema-migrator-ui/src/pages/Drift/DriftPage.tsx`\n> * `schema-migrator-ui/src/pages/Patches/PatchListPage.tsx`\n> * `schema-migrator-ui/src/pages/Runs/RunListPage.tsx`\n> * `schema-migrator-ui/src/pages/Schema/SchemaPage.test.tsx`\n> * `schema-migrator-ui/src/pages/Schema/SchemaPage.tsx`\n> * `schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.test.tsx`\n> * `schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.tsx`\n> * `schema-migrator-ui/src/pages/Targets/TargetListPage.tsx`\n> * `schema-migrator-ui/src/router.tsx`\n> * `schema-migrator-ui/src/styles.css`\n> * `schema-migrator-ui/src/test/render.tsx`\n> * `schema-migrator-ui/src/test/setup.ts`\n> * `schema-migrator-ui/src/types/index.ts`\n> * `src/main/scala/com/sslproxy/schema/cli/CliOpts.scala`\n> * `src/main/scala/com/sslproxy/schema/cli/Commands.scala`\n> * `src/main/scala/com/sslproxy/schema/config/MigratorConfig.scala`\n> * `src/main/scala/com/sslproxy/schema/db/LockManager.scala`\n> * `src/main/scala/com/sslproxy/schema/db/postgres/PostgresProvider.scala`\n> * `src/main/scala/com/sslproxy/schema/discovery/DiscoveryService.scala`\n> * `src/main/scala/com/sslproxy/schema/discovery/SqlFile.scala`\n> * `src/main/scala/com/sslproxy/schema/engine/ManifestBuilder.scala`\n> * `src/main/scala/com/sslproxy/schema/engine/MigrationEngine.scala`\n> * `src/main/scala/com/sslproxy/schema/server/DbPing.scala`\n> * `src/main/scala/com/sslproxy/schema/server/HttpServer.scala`\n> * `src/main/scala/com/sslproxy/schema/server/JdbcConnectionProperties.scala`\n> * `src/main/scala/com/sslproxy/schema/server/LoggingMiddleware.scala`\n> * `src/main/scala/com/sslproxy/schema/server/PostgresDriftAnalyzer.scala`\n> * `src/main/scala/com/sslproxy/schema/server/Routes.scala`\n> * `src/main/scala/com/sslproxy/schema/server/SchemaRoutes.scala`\n> * `src/main/scala/com/sslproxy/schema/server/SqlFileRoutes.scala`\n> * `src/main/scala/com/sslproxy/schema/server/TargetRoutes.scala`\n> * `src/main/scala/com/sslproxy/schema/server/auth/JwtMiddleware.scala`\n> * `src/main/scala/com/sslproxy/schema/server/compress/Bzip2.scala`\n> * `src/main/scala/com/sslproxy/schema/server/compress/Bzip2Middleware.scala`\n> * `src/main/scala/com/sslproxy/schema/store/Models.scala`\n> * `src/main/scala/com/sslproxy/schema/store/MongoTargetStore.scala`\n> * `src/main/scala/com/sslproxy/schema/store/SqlFileStore.scala`\n> * `src/main/scala/com/sslproxy/schema/store/TargetStore.scala`\n> * `src/main/scala/com/sslproxy/schema/validation/DependencyValidator.scala`\n> * `src/main/scala/com/sslproxy/schema/validation/RollbackValidator.scala`\n> * `src/main/scala/com/sslproxy/schema/validation/Validator.scala`\n> * `src/test/scala/com/sslproxy/schema/config/ServerConfigSuite.scala`\n> * `src/test/scala/com/sslproxy/schema/db/ProviderSuite.scala`\n> * `src/test/scala/com/sslproxy/schema/discovery/DiscoveryServiceSuite.scala`\n> * `src/test/scala/com/sslproxy/schema/server/PostgresDriftAnalyzerSuite.scala`\n> * `src/test/scala/com/sslproxy/schema/server/RoutesSuite.scala`\n> * `src/test/scala/com/sslproxy/schema/server/auth/JwtMiddlewareSuite.scala`\n> * `src/test/scala/com/sslproxy/schema/server/compress/Bzip2Suite.scala`\n> * `src/test/scala/com/sslproxy/schema/store/TargetStoreSuite.scala`\n> \n> </details>\n> \n> ```ascii\n>  ________________________________________________________________________________________________________________________\n> < Don't worry if it doesn't work right. If everything did, you'd be out of a job. - Mosher's Law of Software Engineering >\n>  ------------------------------------------------------------------------------------------------------------------------\n>   \\\n>    \\   (\\__/)\n>        (•ㅅ•)\n>        /   づ\n> ```\n\n<!-- end of auto-generated comment: review in progress by coderabbit.ai -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>📝 Generate docstrings</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> Create stacked PR\n- [ ] <!-- {\"checkboxId\": \"3e1879ae-f29b-4d0d-8e06-d12b7ba33d98\"} --> Commit on current branch\n\n</details>\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Commit unit tests in branch `dev`\n\n</details>\n<details open>\n<summary>⚔️ Resolve merge conflicts</summary>\n\n- [ ] <!-- {\"checkboxId\": \"c3a5b2e1-4d7f-4a8c-b9d6-e1f2c3d4a5b6\"} --> Resolve merge conflict in branch `dev`\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=zlovtnik/schema-migrator&utm_content=8)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands.</sub>\n\n<!-- tips_end -->"},"request":{"retryCount":3,"signal":{},"retries":3,"retryAfter":16}}}

@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: 14

🧹 Nitpick comments (10)
AGENTS.md (2)

4-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded personal machine path in scope declaration.

This file governs \/Users/rcs/git/wiretrap/schema-migrator`.bakes in a specific developer's local clone path (usernamercs, under wiretrap`). If this file is used by AI agents/tools to scope their work, this absolute path won't match any other contributor's or CI's checkout location, making the scope declaration ineffective outside this one machine.

📝 Suggested fix using a repo-relative reference
-This file governs `/Users/rcs/git/wiretrap/schema-migrator`.
+This file governs the `schema-migrator` repository (this directory and its subdirectories).
🤖 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 `@AGENTS.md` at line 4, The scope declaration in AGENTS.md uses a hardcoded
absolute local path, which makes it specific to one machine. Replace that path
with a repo-relative reference or a generic workspace-relative description in
the governing scope text so tools and agents can resolve it correctly in any
checkout; update the scope statement itself rather than adding another
machine-specific path.

13-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Markdown list-indent warnings (MD005).

markdownlint flags these list items for inconsistent indentation (leading space before -) versus sibling items at the same level.

Also applies to: 31-36

🤖 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 `@AGENTS.md` around lines 13 - 16, Fix the markdownlint MD005 warnings by
making the list indentation consistent in AGENTS.md. The affected bullet items
in the schema-migrator-ui/docker-compose description and the similar section
noted elsewhere should use the same leading indentation as sibling list entries,
with no extra leading space before the hyphen. Update the surrounding markdown
list formatting so the nested bullets remain structurally correct and consistent
across the document.

Source: Linters/SAST tools

src/main/scala/com/sslproxy/schema/store/SqlFileStore.scala (2)

114-118: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Redundant secondary index on path.

_id is already set to file.path (Line 122), giving an implicit unique index on that value. The separate ascending index on the "path" field indexes the same data again under a different key and adds write overhead without new query capability.

🤖 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/main/scala/com/sslproxy/schema/store/SqlFileStore.scala` around lines 114
- 118, Remove the redundant secondary index creation in the
SqlFileStore.initialize method: keep the compound index on folder and filename,
but drop the separate Indexes.ascending("path") call because file.path is
already stored as _id in the same store and is implicitly indexed. Update the
initialize logic in SqlFileStore so it only creates the index that is still
needed.

97-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate toSqlFiles implementation across both store variants.

The Mongo and in-memory implementations of toSqlFiles are byte-for-byte identical. Extracting a shared helper (e.g., a default method on the SqlFileStore trait built on top of list) would avoid the two copies drifting apart.

♻️ Suggested consolidation
 trait SqlFileStore:
   ...
-  def toSqlFiles: IO[List[SqlFile]]
+  def toSqlFiles: IO[List[SqlFile]] = list.flatMap(toSqlFilesFrom)
+
+  protected def toSqlFilesFrom(storedList: List[StoredSqlFile]): IO[List[SqlFile]] =
+    storedList.traverse { stored =>
+      IO.delay {
+        val bytes = Base64.getDecoder.decode(stored.contentBase64)
+        SqlFile(
+          folder = stored.folder,
+          path = Path.of(stored.path),
+          name = stored.filename,
+          relativePath = stored.path,
+          content = Some(new String(bytes, StandardCharsets.UTF_8))
+        )
+      }
+    }

Then remove the two duplicated override def toSqlFiles bodies.

Also applies to: 161-176

🤖 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/main/scala/com/sslproxy/schema/store/SqlFileStore.scala` around lines 97
- 112, The `toSqlFiles` logic is duplicated in both `SqlFileStore`
implementations, so extract the shared conversion into a common helper on the
`SqlFileStore` trait using `list` as the source and remove the two concrete
`override def toSqlFiles` bodies. Keep the shared decoding and `SqlFile`
construction in one place so the Mongo and in-memory stores both reuse the same
implementation and cannot drift apart.
src/main/scala/com/sslproxy/schema/cli/CliOpts.scala (1)

105-172: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Partial Mongo config collapses to a generic error.

(mongoUri, mongoDatabase, mongoTargetsCollection).mapN(MongoConfig.apply) silently drops to None if only some of the three Mongo options are set, so a user who sets --mongo-uri but forgets --mongo-database gets the same "must be set" message as someone who set nothing, rather than being told which specific value is missing.

Consider validating the three independently and surfacing the missing field name(s) for faster diagnosis.

🤖 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/main/scala/com/sslproxy/schema/cli/CliOpts.scala` around lines 105 - 172,
The Mongo config assembly in `serverOpts` currently uses `(mongoUri,
mongoDatabase, mongoTargetsCollection).mapN(MongoConfig.apply)`, which turns any
partially provided set of options into a generic `None`. Change the validation
around `mongoUriOpt`, `mongoDatabaseOpt`, and `mongoTargetsCollectionOpt` so
each field is checked independently and missing values are reported by name when
any Mongo setting is incomplete. Keep the final `ServerConfig` wiring intact,
but replace the silent `mapN` collapse with explicit validation logic that
preserves a clear error for the specific absent option(s).
.bsp/sbt.json (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Machine-specific generated file committed to the repo.

This BSP config embeds absolute local paths (JDK install dir, IntelliJ version, and a local username) specific to one contributor's machine. It will be invalid/misleading for other contributors and shouldn't be tracked.

🧹 Suggested fix
+.bsp/

Add to .gitignore and remove .bsp/sbt.json from the repo (git rm --cached .bsp/sbt.json).

🤖 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 @.bsp/sbt.json at line 1, The committed BSP config is machine-specific and
should not be tracked because it contains absolute local paths and user-specific
IntelliJ/JDK values. Remove the generated `.bsp/sbt.json` from version control,
add `.bsp/` or this file to `.gitignore`, and keep the repo clean by untracking
the file while leaving local generation to each contributor’s environment.
docker-compose.yml (1)

3-16: 🔒 Security & Privacy | 🔵 Trivial

Consider enabling Mongo authentication for defense-in-depth.

The mongo service runs without root credentials. It's not published to the host, but adding MONGO_INITDB_ROOT_USERNAME/PASSWORD (and matching credentials in BEDROCK_MONGO_URI) would harden the setup against lateral movement within the internal network.

🤖 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 `@docker-compose.yml` around lines 3 - 16, The mongo service is running without
authentication, so harden it by enabling Mongo root credentials in the mongo
service configuration and updating any client connection string to use them. Add
the appropriate initialization environment variables for the mongo container,
then update BEDROCK_MONGO_URI so services connect with matching credentials;
keep the existing mongo service and healthcheck setup aligned with the
authenticated connection.
schema-migrator-ui/src/hooks/useSelectedTarget.tsx (1)

60-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding direct unit tests for this hook.

useSelectedTargetId/SelectedTargetProvider is now shared infrastructure across Drift, Patches, Runs, Schema and TargetSelector. Coverage today is indirect, only exercised through consumer component tests. A focused test (URL-vs-storage precedence, cross-tab storage event sync, invalid/empty normalization) would make regressions in this shared state easier to catch.

🤖 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 `@schema-migrator-ui/src/hooks/useSelectedTarget.tsx` around lines 60 - 72, Add
direct unit tests for useSelectedTargetId and the SelectedTargetProvider shared
state behavior. Cover URL parameter precedence over stored selection,
synchronization via the browser storage event across tabs, and normalization of
empty/invalid values to null. Place the tests near the useSelectedTarget hook so
regressions in this shared infrastructure are caught without relying only on
consumer component tests.
schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.tsx (1)

414-530: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a well-tested zip library instead of a hand-rolled writer.

The manual ZIP writer (local/central headers, CRC32, EOCD) is correct as reviewed, but hand-rolled binary-format code is easy to regress and hard to extend (e.g., no compression, no Zip64 for very large manifests). Libraries like fflate or client-zip provide this with a smaller, well-tested surface area.

🤖 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 `@schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.tsx` around lines 414 -
530, Replace the hand-rolled ZIP assembly in ZipWriter with a well-tested
client-side ZIP library such as fflate or client-zip. Keep the ZipWriter API
shape if needed, but move the binary header/CRC/EOCD logic out of toBytes() and
delegate archive creation to the library so future extensions like compression
and Zip64 are handled safely.
src/main/scala/com/sslproxy/schema/server/DbPing.scala (1)

12-12: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Wiring to JdbcConnectionSettings and redaction logic look correct.

Consider adding unit tests for connectionError/redact (e.g. asserting a postgres://user:secret@host embedded in an exception message is redacted, and that password=/pwd= query values are stripped) since this is the safeguard against leaking credentials in connection-test error responses returned to clients.

Also applies to: 28-33, 45-47, 72-90

🤖 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/main/scala/com/sslproxy/schema/server/DbPing.scala` at line 12, Add unit
tests around DbPing’s connectionError and redact helpers to cover the
credential-scrubbing behavior. Verify that exception messages containing
embedded database URLs like postgres://user:secret@host are redacted before
being returned, and that query parameters such as password= and pwd= are
stripped or masked. Use the existing DbPing/connectionError/redact flow so the
tests exercise the same safeguard used for client-facing connection-test errors.
🤖 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 `@docker-compose.yml`:
- Around line 26-36: The default value for BEDROCK_DB_TEST_ALLOWED_HOSTS in
docker-compose.yml includes a hardcoded private LAN IP, which should not be
committed as a shared default. Update the environment entry to use only generic
local test hosts, and if that host is still needed, make it explicitly
configurable via an override rather than baked into the default. Keep the change
focused on the BEDROCK_DB_TEST_ALLOWED_HOSTS setting in the compose environment
block.

In `@nginx/nginx.conf.template`:
- Around line 1-4: The authorization fallback is currently defined in the
top-level map for $schema_migrator_authorization, which causes
${BEDROCK_API_BEARER_TOKEN} to be added to all proxied requests including /api/.
Update the nginx config so /api/ remains a direct passthrough of
$http_authorization, and move the bearer-token fallback logic into the
^/api/runs/[^/]+/stream$ location only. Use the existing map and location blocks
in nginx.conf.template to scope the fallback narrowly without changing other
request paths.

In `@schema-migrator-ui/package.json`:
- Around line 41-43: Remove the unnecessary workspace declaration from the
standalone manifest by updating the package.json for schema-migrator-ui so it no
longer defines workspaces with ".". This change should be made directly in the
package manifest and verified so npm treats the package as independent; if root
inclusion is needed for workspace commands, rely on npm’s
--include-workspace-root behavior instead of listing the root package in
workspaces.

In `@schema-migrator-ui/src/pages/Drift/DriftPage.tsx`:
- Line 1: The drift-type chip counts are computed from the unfiltered items set,
so they do not match the active text search; update the count calculation in
DriftPage so it uses the same text-filtered items as the table. Adjust the
memoized logic around driftCounts, items, and textFilter so the counts are
derived from the already text-filtered dataset rather than data?.items alone.

In `@schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.tsx`:
- Around line 244-247: The directory picker trigger in SqlFilesPage is using a
focusable label with role="button", but it still won’t open the chooser via
Enter/Space for keyboard users. Update the control to use a native button-style
trigger, or add explicit keyboard handlers for the existing label so activation
works from the keyboard as well as the mouse. Keep the existing behavior around
the sql-dir-picker input and uploading state, but make the trigger in the
SqlFilesPage render path keyboard-activatable.

In `@src/main/scala/com/sslproxy/schema/db/postgres/PostgresProvider.scala`:
- Around line 179-188: `parsePostgresJdbcUrl` is bypassing the host check for
JDBC URLs without credentials, so malformed inputs can slip through `normalize`
and `TargetRoutes.validateTargetPayload`. Update the JDBC shortcut path in
`PostgresProvider.normalize`/`parsePostgresJdbcUrl` to validate that a host is
present before returning `JdbcConnectionConfig`, or route these inputs through
`parsePostgresUri` so the existing host-required logic is applied consistently.

In `@src/main/scala/com/sslproxy/schema/server/compress/Bzip2.scala`:
- Around line 72-75: The error handling in Bzip2’s stream recovery is
incorrectly surfacing incidental writer failures instead of preserving malformed
input as BadInput. In Bzip2.scala, update the failure branch in the
writerFailure.tryGet handling so that invalid compressed bodies always raise
BadInput with the original error, and only let true SizeLimitExceeded cases
propagate separately; do not rethrow writerError from this path because it can
bypass Bzip2Middleware’s BadInput mapping.

In `@src/main/scala/com/sslproxy/schema/server/compress/Bzip2Middleware.scala`:
- Around line 80-91: The responseWithCompressionDecision method buffers the
entire body when Content-Length is missing, which can blow up memory and break
streaming; update the Bzip2Middleware flow to avoid compiling response.body into
a Vector. Instead, either decide compression without full buffering or only peek
up to thresholdBytes + 1 from the stream, and keep the unknown-length path
streaming-friendly while preserving the existing compression decision logic.

In `@src/main/scala/com/sslproxy/schema/server/PostgresDriftAnalyzer.scala`:
- Around line 243-281: PreparedState.apply is using sourceFile as a fallback in
a way that can pick the wrong ControlObject when multiple controls share the
same file. Update PreparedState.apply and its
controlBySource/expectedControlByKey logic so a missing exact key only falls
back to an unambiguous match, or otherwise skips the fallback rather than
reusing a sibling control. Keep the behavior for controlForKey, mergeCatalog,
and driftItems consistent with the intended exact-key resolution, and add
coverage for grouped sourceFile cases to verify the fallback does not carry over
the wrong expectedDdl or applyStatus.

In `@src/main/scala/com/sslproxy/schema/server/SchemaRoutes.scala`:
- Around line 318-321: The catalog key built in SchemaRoutes is currently using
only p.proname, which can merge overloaded functions/procedures in the same
schema. Update the query/object_name construction in the relevant SchemaRoutes
method so it includes the function/procedure signature or identity arguments
alongside the name, preserving uniqueness for pg_get_functiondef-based
comparisons. Ensure the change keeps each overloaded routine distinct in the
catalog key used for drift detection.

In `@src/main/scala/com/sslproxy/schema/server/SqlFileRoutes.scala`:
- Around line 147-161: The file upload flow in fileUploads currently accepts any
multipart filename, which lets non-.sql content reach normalization and manifest
parsing. Add a validation step in fileUploads (before
safeFilename/SqlPathNormalizer.normalizeUploadPath) to reject uploads whose
part.filename does not end with .sql, and return an appropriate upload error.
Use the existing fileUploads, safeFilename, and SqlPathNormalizer symbols to
keep the check close to the current upload handling.
- Around line 68-83: In SqlFileRoutes, the upload flow built around
request.as[Multipart[IO]] and fileUploads currently proceeds to
sqlFileStore.replaceAll(stored) even when stored is empty, which can overwrite
the manifest with nothing. Add an explicit empty-upload guard before replaceAll
in the same route logic, similar to the zip route’s empty-archive check, so that
Nil from fileUploads is rejected and only non-empty uploads reach
StoredSqlFile.fromBytes and sqlFileStore.replaceAll.

In `@src/main/scala/com/sslproxy/schema/store/MongoTargetStore.scala`:
- Around line 72-93: The Mongo target persistence path is storing and retrieving
the database password in plaintext via MongoTargetStore.documentFor and
MongoTargetStore.storedFromDocument. Change this flow so StoredTarget.password
is saved as encrypted ciphertext or a secret reference in MongoDB, and only
decrypted later when building the JDBC connection, keeping the document schema
and any read path in sync with the new secure representation.

In `@src/main/scala/com/sslproxy/schema/store/SqlFileStore.scala`:
- Around line 82-89: `SqlFileStore.replaceAll` is non-atomic because it calls
`deleteMany` before `insertMany`, so a failure in the second step can erase the
stored manifest; update this method to use an atomic or staged swap approach
instead. Prefer a MongoDB transaction in `replaceAll` if supported, or write the
new `StoredSqlFile` documents first and only delete stale records after the new
writes succeed, using the existing `collection` and `toDocument` flow so the
manifest is never left empty on partial failure.

---

Nitpick comments:
In @.bsp/sbt.json:
- Line 1: The committed BSP config is machine-specific and should not be tracked
because it contains absolute local paths and user-specific IntelliJ/JDK values.
Remove the generated `.bsp/sbt.json` from version control, add `.bsp/` or this
file to `.gitignore`, and keep the repo clean by untracking the file while
leaving local generation to each contributor’s environment.

In `@AGENTS.md`:
- Line 4: The scope declaration in AGENTS.md uses a hardcoded absolute local
path, which makes it specific to one machine. Replace that path with a
repo-relative reference or a generic workspace-relative description in the
governing scope text so tools and agents can resolve it correctly in any
checkout; update the scope statement itself rather than adding another
machine-specific path.
- Around line 13-16: Fix the markdownlint MD005 warnings by making the list
indentation consistent in AGENTS.md. The affected bullet items in the
schema-migrator-ui/docker-compose description and the similar section noted
elsewhere should use the same leading indentation as sibling list entries, with
no extra leading space before the hyphen. Update the surrounding markdown list
formatting so the nested bullets remain structurally correct and consistent
across the document.

In `@docker-compose.yml`:
- Around line 3-16: The mongo service is running without authentication, so
harden it by enabling Mongo root credentials in the mongo service configuration
and updating any client connection string to use them. Add the appropriate
initialization environment variables for the mongo container, then update
BEDROCK_MONGO_URI so services connect with matching credentials; keep the
existing mongo service and healthcheck setup aligned with the authenticated
connection.

In `@schema-migrator-ui/src/hooks/useSelectedTarget.tsx`:
- Around line 60-72: Add direct unit tests for useSelectedTargetId and the
SelectedTargetProvider shared state behavior. Cover URL parameter precedence
over stored selection, synchronization via the browser storage event across
tabs, and normalization of empty/invalid values to null. Place the tests near
the useSelectedTarget hook so regressions in this shared infrastructure are
caught without relying only on consumer component tests.

In `@schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.tsx`:
- Around line 414-530: Replace the hand-rolled ZIP assembly in ZipWriter with a
well-tested client-side ZIP library such as fflate or client-zip. Keep the
ZipWriter API shape if needed, but move the binary header/CRC/EOCD logic out of
toBytes() and delegate archive creation to the library so future extensions like
compression and Zip64 are handled safely.

In `@src/main/scala/com/sslproxy/schema/cli/CliOpts.scala`:
- Around line 105-172: The Mongo config assembly in `serverOpts` currently uses
`(mongoUri, mongoDatabase, mongoTargetsCollection).mapN(MongoConfig.apply)`,
which turns any partially provided set of options into a generic `None`. Change
the validation around `mongoUriOpt`, `mongoDatabaseOpt`, and
`mongoTargetsCollectionOpt` so each field is checked independently and missing
values are reported by name when any Mongo setting is incomplete. Keep the final
`ServerConfig` wiring intact, but replace the silent `mapN` collapse with
explicit validation logic that preserves a clear error for the specific absent
option(s).

In `@src/main/scala/com/sslproxy/schema/server/DbPing.scala`:
- Line 12: Add unit tests around DbPing’s connectionError and redact helpers to
cover the credential-scrubbing behavior. Verify that exception messages
containing embedded database URLs like postgres://user:secret@host are redacted
before being returned, and that query parameters such as password= and pwd= are
stripped or masked. Use the existing DbPing/connectionError/redact flow so the
tests exercise the same safeguard used for client-facing connection-test errors.

In `@src/main/scala/com/sslproxy/schema/store/SqlFileStore.scala`:
- Around line 114-118: Remove the redundant secondary index creation in the
SqlFileStore.initialize method: keep the compound index on folder and filename,
but drop the separate Indexes.ascending("path") call because file.path is
already stored as _id in the same store and is implicitly indexed. Update the
initialize logic in SqlFileStore so it only creates the index that is still
needed.
- Around line 97-112: The `toSqlFiles` logic is duplicated in both
`SqlFileStore` implementations, so extract the shared conversion into a common
helper on the `SqlFileStore` trait using `list` as the source and remove the two
concrete `override def toSqlFiles` bodies. Keep the shared decoding and
`SqlFile` construction in one place so the Mongo and in-memory stores both reuse
the same implementation and cannot drift apart.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a2803d98-7785-4155-9c0c-b25094d1df3f

📥 Commits

Reviewing files that changed from the base of the PR and between b1c1dba and b81108a.

⛔ Files ignored due to path filters (2)
  • schema-migrator-ui/bun.lock is excluded by !**/*.lock
  • schema-migrator-ui/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (71)
  • .bsp/sbt.json
  • AGENTS.md
  • README.md
  • build.sbt
  • docker-compose.yml
  • nginx/Dockerfile
  • nginx/nginx.conf.template
  • schema-migrator-ui/package.json
  • schema-migrator-ui/src/api/sqlFiles.ts
  • schema-migrator-ui/src/components/AppBreadcrumbs.test.ts
  • schema-migrator-ui/src/components/AppBreadcrumbs.tsx
  • schema-migrator-ui/src/components/ConnectionForm.tsx
  • schema-migrator-ui/src/components/DocumentTitle.test.ts
  • schema-migrator-ui/src/components/DocumentTitle.tsx
  • schema-migrator-ui/src/components/TargetSelector.tsx
  • schema-migrator-ui/src/components/breadcrumbs.ts
  • schema-migrator-ui/src/components/titleFormatting.ts
  • schema-migrator-ui/src/components/ui/DataTable.tsx
  • schema-migrator-ui/src/hooks/useSelectedTarget.tsx
  • schema-migrator-ui/src/layouts/AppShell.tsx
  • schema-migrator-ui/src/main.tsx
  • schema-migrator-ui/src/pages/Drift/DriftPage.test.tsx
  • schema-migrator-ui/src/pages/Drift/DriftPage.tsx
  • schema-migrator-ui/src/pages/Patches/PatchListPage.tsx
  • schema-migrator-ui/src/pages/Runs/RunListPage.tsx
  • schema-migrator-ui/src/pages/Schema/SchemaPage.test.tsx
  • schema-migrator-ui/src/pages/Schema/SchemaPage.tsx
  • schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.test.tsx
  • schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.tsx
  • schema-migrator-ui/src/pages/Targets/TargetListPage.tsx
  • schema-migrator-ui/src/router.tsx
  • schema-migrator-ui/src/styles.css
  • schema-migrator-ui/src/test/render.tsx
  • schema-migrator-ui/src/test/setup.ts
  • schema-migrator-ui/src/types/index.ts
  • src/main/scala/com/sslproxy/schema/cli/CliOpts.scala
  • src/main/scala/com/sslproxy/schema/cli/Commands.scala
  • src/main/scala/com/sslproxy/schema/config/MigratorConfig.scala
  • src/main/scala/com/sslproxy/schema/db/LockManager.scala
  • src/main/scala/com/sslproxy/schema/db/postgres/PostgresProvider.scala
  • src/main/scala/com/sslproxy/schema/discovery/DiscoveryService.scala
  • src/main/scala/com/sslproxy/schema/discovery/SqlFile.scala
  • src/main/scala/com/sslproxy/schema/engine/ManifestBuilder.scala
  • src/main/scala/com/sslproxy/schema/engine/MigrationEngine.scala
  • src/main/scala/com/sslproxy/schema/server/DbPing.scala
  • src/main/scala/com/sslproxy/schema/server/HttpServer.scala
  • src/main/scala/com/sslproxy/schema/server/JdbcConnectionProperties.scala
  • src/main/scala/com/sslproxy/schema/server/LoggingMiddleware.scala
  • src/main/scala/com/sslproxy/schema/server/PostgresDriftAnalyzer.scala
  • src/main/scala/com/sslproxy/schema/server/Routes.scala
  • src/main/scala/com/sslproxy/schema/server/SchemaRoutes.scala
  • src/main/scala/com/sslproxy/schema/server/SqlFileRoutes.scala
  • src/main/scala/com/sslproxy/schema/server/TargetRoutes.scala
  • src/main/scala/com/sslproxy/schema/server/auth/JwtMiddleware.scala
  • src/main/scala/com/sslproxy/schema/server/compress/Bzip2.scala
  • src/main/scala/com/sslproxy/schema/server/compress/Bzip2Middleware.scala
  • src/main/scala/com/sslproxy/schema/store/Models.scala
  • src/main/scala/com/sslproxy/schema/store/MongoTargetStore.scala
  • src/main/scala/com/sslproxy/schema/store/SqlFileStore.scala
  • src/main/scala/com/sslproxy/schema/store/TargetStore.scala
  • src/main/scala/com/sslproxy/schema/validation/DependencyValidator.scala
  • src/main/scala/com/sslproxy/schema/validation/RollbackValidator.scala
  • src/main/scala/com/sslproxy/schema/validation/Validator.scala
  • src/test/scala/com/sslproxy/schema/config/ServerConfigSuite.scala
  • src/test/scala/com/sslproxy/schema/db/ProviderSuite.scala
  • src/test/scala/com/sslproxy/schema/discovery/DiscoveryServiceSuite.scala
  • src/test/scala/com/sslproxy/schema/server/PostgresDriftAnalyzerSuite.scala
  • src/test/scala/com/sslproxy/schema/server/RoutesSuite.scala
  • src/test/scala/com/sslproxy/schema/server/auth/JwtMiddlewareSuite.scala
  • src/test/scala/com/sslproxy/schema/server/compress/Bzip2Suite.scala
  • src/test/scala/com/sslproxy/schema/store/TargetStoreSuite.scala

Comment thread docker-compose.yml
Comment on lines +26 to +36
- BEDROCK_JWT_SECRET=${BEDROCK_JWT_SECRET:?BEDROCK_JWT_SECRET is required}
- BEDROCK_DEV_AUTH_SECRET=${BEDROCK_DEV_AUTH_SECRET:?BEDROCK_DEV_AUTH_SECRET is required}
- BEDROCK_API_BEARER_TOKEN=${BEDROCK_API_BEARER_TOKEN:?BEDROCK_API_BEARER_TOKEN is required}
- BEDROCK_MONGO_URI=${BEDROCK_MONGO_URI:-mongodb://mongo:27017}
- BEDROCK_MONGO_DATABASE=${BEDROCK_MONGO_DATABASE:-schema_migrator}
- BEDROCK_MONGO_TARGETS_COLLECTION=${BEDROCK_MONGO_TARGETS_COLLECTION:-targets}
- BEDROCK_DB_TEST_ALLOWED_HOSTS=${BEDROCK_DB_TEST_ALLOWED_HOSTS:-localhost,127.0.0.1,192.168.1.221}
- BEDROCK_DB_URL=${BEDROCK_DB_URL:?BEDROCK_DB_URL is required}
- BEDROCK_DB_USER=${BEDROCK_DB_USER:?BEDROCK_DB_USER is required}
- BEDROCK_DB_PASSWORD=${BEDROCK_DB_PASSWORD:?BEDROCK_DB_PASSWORD is required}
- DATABASE_URL=${BEDROCK_DB_URL:?BEDROCK_DB_URL is required}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Hardcoded private IP in shared default.

BEDROCK_DB_TEST_ALLOWED_HOSTS defaults to localhost,127.0.0.1,192.168.1.221. The specific 192.168.1.221 looks like a leftover developer/LAN IP baked into a committed default rather than a generic value, and will silently allowlist that host for anyone who doesn't override the variable.

💡 Suggested fix
-      - BEDROCK_DB_TEST_ALLOWED_HOSTS=${BEDROCK_DB_TEST_ALLOWED_HOSTS:-localhost,127.0.0.1,192.168.1.221}
+      - BEDROCK_DB_TEST_ALLOWED_HOSTS=${BEDROCK_DB_TEST_ALLOWED_HOSTS:-localhost,127.0.0.1}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- BEDROCK_JWT_SECRET=${BEDROCK_JWT_SECRET:?BEDROCK_JWT_SECRET is required}
- BEDROCK_DEV_AUTH_SECRET=${BEDROCK_DEV_AUTH_SECRET:?BEDROCK_DEV_AUTH_SECRET is required}
- BEDROCK_API_BEARER_TOKEN=${BEDROCK_API_BEARER_TOKEN:?BEDROCK_API_BEARER_TOKEN is required}
- BEDROCK_MONGO_URI=${BEDROCK_MONGO_URI:-mongodb://mongo:27017}
- BEDROCK_MONGO_DATABASE=${BEDROCK_MONGO_DATABASE:-schema_migrator}
- BEDROCK_MONGO_TARGETS_COLLECTION=${BEDROCK_MONGO_TARGETS_COLLECTION:-targets}
- BEDROCK_DB_TEST_ALLOWED_HOSTS=${BEDROCK_DB_TEST_ALLOWED_HOSTS:-localhost,127.0.0.1,192.168.1.221}
- BEDROCK_DB_URL=${BEDROCK_DB_URL:?BEDROCK_DB_URL is required}
- BEDROCK_DB_USER=${BEDROCK_DB_USER:?BEDROCK_DB_USER is required}
- BEDROCK_DB_PASSWORD=${BEDROCK_DB_PASSWORD:?BEDROCK_DB_PASSWORD is required}
- DATABASE_URL=${BEDROCK_DB_URL:?BEDROCK_DB_URL is required}
- BEDROCK_JWT_SECRET=${BEDROCK_JWT_SECRET:?BEDROCK_JWT_SECRET is required}
- BEDROCK_DEV_AUTH_SECRET=${BEDROCK_DEV_AUTH_SECRET:?BEDROCK_DEV_AUTH_SECRET is required}
- BEDROCK_API_BEARER_TOKEN=${BEDROCK_API_BEARER_TOKEN:?BEDROCK_API_BEARER_TOKEN is required}
- BEDROCK_MONGO_URI=${BEDROCK_MONGO_URI:-mongodb://mongo:27017}
- BEDROCK_MONGO_DATABASE=${BEDROCK_MONGO_DATABASE:-schema_migrator}
- BEDROCK_MONGO_TARGETS_COLLECTION=${BEDROCK_MONGO_TARGETS_COLLECTION:-targets}
- BEDROCK_DB_TEST_ALLOWED_HOSTS=${BEDROCK_DB_TEST_ALLOWED_HOSTS:-localhost,127.0.0.1}
- BEDROCK_DB_URL=${BEDROCK_DB_URL:?BEDROCK_DB_URL is required}
- BEDROCK_DB_USER=${BEDROCK_DB_USER:?BEDROCK_DB_USER is required}
- BEDROCK_DB_PASSWORD=${BEDROCK_DB_PASSWORD:?BEDROCK_DB_PASSWORD is required}
- DATABASE_URL=${BEDROCK_DB_URL:?BEDROCK_DB_URL is required}
🤖 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 `@docker-compose.yml` around lines 26 - 36, The default value for
BEDROCK_DB_TEST_ALLOWED_HOSTS in docker-compose.yml includes a hardcoded private
LAN IP, which should not be committed as a shared default. Update the
environment entry to use only generic local test hosts, and if that host is
still needed, make it explicitly configurable via an override rather than baked
into the default. Keep the change focused on the BEDROCK_DB_TEST_ALLOWED_HOSTS
setting in the compose environment block.

Comment thread nginx/nginx.conf.template
Comment thread schema-migrator-ui/package.json Outdated
Comment thread schema-migrator-ui/src/pages/Drift/DriftPage.tsx Outdated
Comment thread schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.tsx Outdated
Comment thread src/main/scala/com/sslproxy/schema/server/SchemaRoutes.scala
Comment thread src/main/scala/com/sslproxy/schema/server/SqlFileRoutes.scala Outdated
Comment thread src/main/scala/com/sslproxy/schema/server/SqlFileRoutes.scala
Comment thread src/main/scala/com/sslproxy/schema/store/MongoTargetStore.scala Outdated
Comment thread src/main/scala/com/sslproxy/schema/store/SqlFileStore.scala

@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: 2

🧹 Nitpick comments (1)
schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.tsx (1)

411-431: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Drop the extra ZIP buffer copy zipSync already returns a Uint8Array, so this allocates and copies the whole archive again. Return the zipSync result directly, or cast it if you need the narrower Uint8Array<ArrayBuffer> annotation.

🤖 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 `@schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.tsx` around lines 411 -
431, The ZipWriter::toBytes method is making an unnecessary second copy of the
ZIP archive after zipSync already returns the bytes. Update ZipWriter so toBytes
returns the zipSync result directly, or adjust the return type annotation if
needed, and keep toBlob using that single buffer path without reallocating or
copying the archive data again.
🤖 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/main/scala/com/sslproxy/schema/store/MongoTargetStore.scala`:
- Around line 17-27: The MongoTargetStore.resource wiring currently accepts a
passwordKey that is reused from the HTTP response encryption key, so update the
call site and related key-loading logic to use a distinct key for target
password encryption. Introduce separate key material with clear domain
separation, such as a dedicated BEDROCK_TARGET_PASSWORD_KEY or an HKDF-derived
subkey, and keep the MongoTargetStore and PasswordCrypto usage unchanged except
for passing the new target-password-specific key. Identify the key plumbing in
HttpServer.scala and the MongoTargetStore.resource signature so the encryption
domains are no longer shared.
- Around line 57-70: `MongoTargetStore.update` currently does a non-atomic
read-then-write using `collection.find(...).first()` and
`collection.replaceOne(...)`, which can overwrite concurrent changes. Refactor
this path to use a single atomic MongoDB operation in `update` (prefer
`findOneAndReplace` or an equivalent atomic replace on the same `idFilter(id)`),
and build the replacement from the current persisted document returned by that
operation instead of a stale snapshot. Keep the existing helpers like
`storedFromDocument`, `documentFor`, and `toTarget` in place, but wire them
through the atomic call so the update logic in `MongoTargetStore` no longer
depends on separate read and write round-trips.

---

Nitpick comments:
In `@schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.tsx`:
- Around line 411-431: The ZipWriter::toBytes method is making an unnecessary
second copy of the ZIP archive after zipSync already returns the bytes. Update
ZipWriter so toBytes returns the zipSync result directly, or adjust the return
type annotation if needed, and keep toBlob using that single buffer path without
reallocating or copying the archive data again.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3e45a1b7-f1b0-4486-a12f-f00277fdce8d

📥 Commits

Reviewing files that changed from the base of the PR and between b81108a and 820a7c9.

⛔ Files ignored due to path filters (2)
  • schema-migrator-ui/bun.lock is excluded by !**/*.lock
  • schema-migrator-ui/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • .gitignore
  • AGENTS.md
  • README.md
  • nginx/nginx.conf.template
  • schema-migrator-ui/package.json
  • schema-migrator-ui/src/hooks/useSelectedTarget.test.tsx
  • schema-migrator-ui/src/hooks/useSelectedTarget.tsx
  • schema-migrator-ui/src/pages/Drift/DriftPage.test.tsx
  • schema-migrator-ui/src/pages/Drift/DriftPage.tsx
  • schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.test.tsx
  • schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.tsx
  • src/main/scala/com/sslproxy/schema/cli/CliOpts.scala
  • src/main/scala/com/sslproxy/schema/config/MigratorConfig.scala
  • src/main/scala/com/sslproxy/schema/db/postgres/PostgresProvider.scala
  • src/main/scala/com/sslproxy/schema/server/DbPing.scala
  • src/main/scala/com/sslproxy/schema/server/HttpServer.scala
  • src/main/scala/com/sslproxy/schema/server/PostgresDriftAnalyzer.scala
  • src/main/scala/com/sslproxy/schema/server/SchemaRoutes.scala
  • src/main/scala/com/sslproxy/schema/server/SqlFileRoutes.scala
  • src/main/scala/com/sslproxy/schema/server/compress/Bzip2Middleware.scala
  • src/main/scala/com/sslproxy/schema/store/MongoTargetStore.scala
  • src/main/scala/com/sslproxy/schema/store/SqlFileStore.scala
  • src/main/scala/com/sslproxy/schema/store/TargetStore.scala
  • src/test/scala/com/sslproxy/schema/config/ServerConfigSuite.scala
  • src/test/scala/com/sslproxy/schema/db/ProviderSuite.scala
  • src/test/scala/com/sslproxy/schema/server/DbPingSuite.scala
  • src/test/scala/com/sslproxy/schema/server/PostgresDriftAnalyzerSuite.scala
  • src/test/scala/com/sslproxy/schema/server/RoutesSuite.scala
  • src/test/scala/com/sslproxy/schema/server/compress/Bzip2Suite.scala
  • src/test/scala/com/sslproxy/schema/store/TargetStoreSuite.scala
✅ Files skipped from review due to trivial changes (3)
  • .gitignore
  • AGENTS.md
  • README.md
🚧 Files skipped from review as they are similar to previous changes (18)
  • src/test/scala/com/sslproxy/schema/db/ProviderSuite.scala
  • nginx/nginx.conf.template
  • src/main/scala/com/sslproxy/schema/config/MigratorConfig.scala
  • src/test/scala/com/sslproxy/schema/config/ServerConfigSuite.scala
  • src/test/scala/com/sslproxy/schema/store/TargetStoreSuite.scala
  • src/main/scala/com/sslproxy/schema/db/postgres/PostgresProvider.scala
  • schema-migrator-ui/src/pages/SqlFiles/SqlFilesPage.test.tsx
  • src/main/scala/com/sslproxy/schema/cli/CliOpts.scala
  • src/main/scala/com/sslproxy/schema/server/SqlFileRoutes.scala
  • src/main/scala/com/sslproxy/schema/server/DbPing.scala
  • schema-migrator-ui/src/hooks/useSelectedTarget.tsx
  • src/test/scala/com/sslproxy/schema/server/RoutesSuite.scala
  • src/main/scala/com/sslproxy/schema/store/SqlFileStore.scala
  • schema-migrator-ui/src/pages/Drift/DriftPage.test.tsx
  • schema-migrator-ui/src/pages/Drift/DriftPage.tsx
  • src/test/scala/com/sslproxy/schema/server/PostgresDriftAnalyzerSuite.scala
  • src/main/scala/com/sslproxy/schema/server/SchemaRoutes.scala
  • src/main/scala/com/sslproxy/schema/server/PostgresDriftAnalyzer.scala

Comment on lines +17 to +27
object MongoTargetStore:
def resource(config: MongoConfig, passwordKey: SecretKeySpec): Resource[IO, TargetStore] =
Resource
.make(IO.blocking(MongoClients.create(config.uri)))(client => IO.blocking(client.close()))
.evalMap { client =>
val store = MongoTargetStore(
client.getDatabase(config.database).getCollection(config.targetsCollection),
new PasswordCrypto(passwordKey)
)
store.initialize.as(store: TargetStore)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Target password encryption key reuses the HTTP response encryption key.

Per HttpServer.scala's wiring (evidence provided), targetPasswordKey passed here is the same encryptKey used for AesGcmMiddleware response encryption. Sharing one AES-GCM key across two unrelated security domains (transient HTTP payload encryption vs. durable password-at-rest encryption) violates key-separation principles: a compromise or rotation need in one domain forces action in the other, and blast radius is unnecessarily widened.

Consider deriving a distinct key for target password encryption (e.g., a separate BEDROCK_TARGET_PASSWORD_KEY, or an HKDF-derived subkey from the master secret with a domain-separation label) rather than reusing BEDROCK_ENCRYPT_KEY for both purposes.

🤖 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/main/scala/com/sslproxy/schema/store/MongoTargetStore.scala` around lines
17 - 27, The MongoTargetStore.resource wiring currently accepts a passwordKey
that is reused from the HTTP response encryption key, so update the call site
and related key-loading logic to use a distinct key for target password
encryption. Introduce separate key material with clear domain separation, such
as a dedicated BEDROCK_TARGET_PASSWORD_KEY or an HKDF-derived subkey, and keep
the MongoTargetStore and PasswordCrypto usage unchanged except for passing the
new target-password-specific key. Identify the key plumbing in HttpServer.scala
and the MongoTargetStore.resource signature so the encryption domains are no
longer shared.

Comment on lines +57 to +70
override def update(id: String, payload: TargetPayload): IO[Option[Target]] =
for
document <- IO.blocking(Option(collection.find(idFilter(id)).first()))
updated <- document.traverse { document =>
for
existing <- storedFromDocument(document)
now <- nowString
target = toTarget(id, existing.target.created_at, payload)
password = payload.password.filter(_.nonEmpty).orElse(existing.password)
replacement <- documentFor(StoredTarget(target, password), now)
result <- IO.blocking(collection.replaceOne(idFilter(id), replacement))
yield Option.when(result.getMatchedCount > 0)(target)
}.map(_.flatten)
yield updated

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Non-atomic read-then-write in update allows lost updates.

find().first() followed by a separate replaceOne(idFilter(id), replacement) is not atomic. A concurrent update to the same target between these two calls will be silently overwritten, since the replacement is built from a stale snapshot (existing.password, existing.target.created_at). The prior in-memory Ref-based store did not have this race.

💡 Suggested fix: use an atomic findOneAndReplace
   override def update(id: String, payload: TargetPayload): IO[Option[Target]] =
     for
       document <- IO.blocking(Option(collection.find(idFilter(id)).first()))
       updated <- document.traverse { document =>
         for
           existing <- storedFromDocument(document)
           now <- nowString
           target = toTarget(id, existing.target.created_at, payload)
           password = payload.password.filter(_.nonEmpty).orElse(existing.password)
           replacement <- documentFor(StoredTarget(target, password), now)
-          result <- IO.blocking(collection.replaceOne(idFilter(id), replacement))
-        yield Option.when(result.getMatchedCount > 0)(target)
+          replaced <- IO.blocking(Option(collection.findOneAndReplace(idFilter(id), replacement)))
+        yield replaced.as(target)
       }.map(_.flatten)
     yield updated

Note this collapses the write into one atomic round-trip; full protection against concurrent edits to the same target would additionally need optimistic concurrency (e.g., matching on an updated_at/version field in the filter).

🤖 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/main/scala/com/sslproxy/schema/store/MongoTargetStore.scala` around lines
57 - 70, `MongoTargetStore.update` currently does a non-atomic read-then-write
using `collection.find(...).first()` and `collection.replaceOne(...)`, which can
overwrite concurrent changes. Refactor this path to use a single atomic MongoDB
operation in `update` (prefer `findOneAndReplace` or an equivalent atomic
replace on the same `idFilter(id)`), and build the replacement from the current
persisted document returned by that operation instead of a stale snapshot. Keep
the existing helpers like `storedFromDocument`, `documentFor`, and `toTarget` in
place, but wire them through the atomic call so the update logic in
`MongoTargetStore` no longer depends on separate read and write round-trips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant