Skip to content

feat(provider): Akahu integration#1921

Open
failing wants to merge 12 commits into
we-promise:mainfrom
failing:akahu
Open

feat(provider): Akahu integration#1921
failing wants to merge 12 commits into
we-promise:mainfrom
failing:akahu

Conversation

@failing
Copy link
Copy Markdown

@failing failing commented May 22, 2026

Summary

Akahu is a NZ open banking aggregator with the ability to to connect to various banks and other financial institutions.

Changes

  • Adding new provider
  • Support provider having more than one "kind"

Screenshots

Connection Sidebar Connected
connection_sidebar connected
Connection Setup Connection Search
connection_setup connection_search
Account Setup Account List
account_setup account_view_1

Summary by CodeRabbit

  • New Features

    • Akahu provider integration: connect accounts, guided setup/linking flows, scheduled syncs, sync-health UI panel, accounts listing, and background sync orchestration
    • Provider cards support multiple kinds for filtering/search
  • Bug Fixes

    • Treat Akahu pending flags as pending for reconciliation and status/search filters
  • Documentation

    • Added Akahu UI copy and setup instructions
  • Tests

    • Extensive tests covering Akahu flows, importers, processors, syncer, metadata, and UI interactions

Review Change Stack

@superagent-security superagent-security Bot added the contributor:verified Contributor passed trust analysis. label May 22, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds full Akahu provider integration: routes, controller flows (link/setup/sync), AkahuItem/AkahuAccount models, importer/processors for accounts and transactions, Provider::Akahu client and adapter, provider metadata change to multi-kind, DB migration/schema updates, reconciliation updates for Akahu pending flags, UI panels/partials, and tests.

Changes

Akahu provider integration

Layer / File(s) Summary
Provider card & frontend filter
app/components/settings/provider_card.rb, app/javascript/controllers/providers_filter_controller.js, app/views/settings/providers/show.html.erb, test/components/settings/provider_card_test.rb
Provider card now accepts kinds: array; meta_line and filter_data use multiple kinds; JS filter splits providerKind into tokens for matching and free-text search; tests cover multi/single-kind behavior.
Routes, controllers, views
config/routes.rb, app/controllers/akahu_items_controller.rb, app/views/akahu_items/*, app/views/settings/providers/_akahu_panel.html.erb, app/views/settings/providers/show.html.erb, app/views/accounts/index.html.erb
New akahu_items routes and AkahuItemsController implement CRUD, sync, account preload/link/setup flows, safe return-path validation, turbo-aware panel rendering, and corresponding modal/partial views for selecting/linking/setup.
AkahuItem & AkahuAccount models & concerns
app/models/akahu_item.rb, app/models/akahu_account.rb, app/models/akahu_item/provided.rb, app/models/akahu_item/unlinking.rb, app/models/family/akahu_connectable.rb
New AkahuItem and AkahuAccount AR models with snapshots, upsert helpers, conditional encryption, associations, sync helpers, unlinking concern, and family connectable concern for creation and credential checks.
Importer, processors, entry import
app/models/akahu_item/importer.rb, app/models/akahu_account/processor.rb, app/models/akahu_account/transactions/processor.rb, app/models/akahu_entry/processor.rb, app/models/akahu_item/syncer.rb, app/models/akahu_item/sync_complete_event.rb
Implements import orchestration (posted + pending), deduplication/storage keys, transaction processing, pending pruning, AkahuEntry processing with deterministic external IDs, per-account processors, sync orchestration and error-normalization for health stats, and sync-complete broadcasting.
Provider client, adapter, metadata & status wiring
app/models/provider/akahu.rb, app/models/provider/akahu_adapter.rb, app/models/provider/metadata.rb, app/models/provider_connection_status.rb, app/controllers/settings/providers_controller.rb, app/helpers/settings_helper.rb
New Provider::Akahu client with retries/pagination and typed errors; Provider::AkahuAdapter to build providers and expose routing/config; provider metadata registry switched from kind: to kinds: arrays; ProviderConnectionStatus extended with akahu.
Pending reconciliation & transaction flags
app/models/account/provider_import_adapter.rb, app/models/transaction.rb, app/models/transaction/search.rb, app/models/entry_search.rb
Pending detection and pending→posted reconciliation SQL updated to consider Akahu pending flags (extra['akahu']['pending']); Transaction::PENDING_PROVIDERS and search filters extended.
Migrations & schema
db/migrate/20260522120000_create_akahu_items_and_accounts.rb, db/migrate/20260522121000_remove_legacy_recurring_transactions_family_unique_index.rb, db/schema.rb
Adds migrations to create akahu_items and akahu_accounts, removes legacy recurring_transactions index, and updates schema snapshot including new tables/foreign keys and minor schema reorders.
Tests
test/**/*akahu*, test/components/settings/provider_card_test.rb, test/models/provider/akahu_test.rb, and many added tests under test/models and test/controllers
Adds broad test coverage for Akahu client, adapter, importer, processors, syncer, controller flows (setup/link), provider metadata, provider-card multi-kind UI, unlinking behavior, and importer/processor error sanitization.

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

Possibly related issues:

Possibly related PRs:

Suggested reviewers:

  • jjmata

"I hopped through tokens, kinds in a row,
New APIs fetched where transactions flow.
I nudged the cards and tied the thread,
Now Akahu hops into the bed.
— a happy rabbit 🥕"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@superagent-security superagent-security Bot added the pr:flagged PR flagged for review by security analysis. label May 22, 2026
Copy link
Copy Markdown

@superagent-security superagent-security Bot left a comment

Choose a reason for hiding this comment

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

Superagent found 1 security concern(s).

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d67dceec04

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread app/models/akahu_item/importer.rb Outdated
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: 14

🧹 Nitpick comments (1)
test/models/provider/metadata_test.rb (1)

8-12: ⚡ Quick win

Avoid hard-coding exclusivity for multi-kind providers.

This test encodes a product assumption (“only akahu can be multi-kind”) that will create false failures when another provider adds multiple kinds.

💡 Suggested patch
-  test "akahu is the only provider with multiple kinds" do
-    providers_with_multiple_kinds = Provider::Metadata::REGISTRY.select { |_provider_key, metadata| metadata[:kinds].size > 1 }
-
-    assert_equal [ :akahu ], providers_with_multiple_kinds.keys
+  test "akahu supports multiple kinds" do
+    providers_with_multiple_kinds = Provider::Metadata::REGISTRY.select { |_provider_key, metadata| metadata[:kinds].size > 1 }
+
+    assert_includes providers_with_multiple_kinds.keys, :akahu
   end
🤖 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 `@test/models/provider/metadata_test.rb` around lines 8 - 12, The test "akahu
is the only provider with multiple kinds" hard-codes exclusivity; update it to
assert that :akahu is present among providers with multiple kinds instead of
being the sole one. Modify the assertion against Provider::Metadata::REGISTRY
(the providers_with_multiple_kinds calculation) to use an inclusion assertion
(e.g., assert_includes providers_with_multiple_kinds.keys, :akahu) and, if
desired, also assert that providers_with_multiple_kinds is not empty to preserve
the intent that multi-kind providers exist.
🤖 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 `@app/controllers/akahu_items_controller.rb`:
- Around line 25-27: Replace the fallback assignment that uses ||= (which won't
override an empty string) by checking blank? on the model's name and assign a
localized default; specifically, after building `@akahu_item` via
Current.family.akahu_items.build(akahu_item_params) change the logic to set
`@akahu_item.name` = t('akahu_items.default_name') only when
`@akahu_item.name.blank`? so it covers empty strings and uses the existing i18n
key.
- Around line 44-52: The destroy action currently calls
`@akahu_item.unlink_all`!(dry_run: false) and proceeds to call
`@akahu_item.destroy_later` and redirect with a success notice even when unlinking
raises; change the rescue so that on any exception from unlink_all! you do NOT
call `@akahu_item.destroy_later` and instead redirect (or render) to
settings_providers_path with an error/alert message and an appropriate status
(e.g. :unprocessable_entity or :see_other) so the item is not destroyed when
unlinking fails; update the destroy method to only call destroy_later and
success redirect when unlink_all! completes without error and surface the
exception class/message (from e) in the error log and user-facing alert.

In `@app/models/akahu_account.rb`:
- Around line 47-51: The fallback literal "Akahu account" used when building
display_name should be replaced with an i18n lookup (use
t('akahu_account.fallback') or similar) so the user-facing string is localized;
update the display_name expression to call the t() helper instead of the
hard-coded string and add the corresponding key (e.g. akahu_account.fallback:
"Akahu account") to config/locales/en.yml. Ensure you use presence fallbacks
exactly as before (snapshot[:name].presence || connection[:name].presence ||
t(...)) and run tests/locale checks after adding the new key.
- Line 59: The account_id assignment currently forces a string by calling .to_s
on (snapshot[:_id].presence || snapshot[:id].presence), which converts nil to an
empty string; change the assignment for account_id to keep nil when no id is
present by removing the .to_s call so it uses (snapshot[:_id].presence ||
snapshot[:id].presence) directly; update the AkahuAccount creation/assignment
site where account_id is set to avoid persisting "" and rely on presence to
return nil when absent.

In `@app/models/akahu_item.rb`:
- Around line 87-99: The method sync_status_summary currently returns hard-coded
English strings; replace these with i18n lookups using the Rails t() helper and
pluralization/interpolation: call t('akahu_item.sync_status.no_accounts') when
total_accounts.zero?, t('akahu_item.sync_status.all_synced', count:
linked_count) for the linked-only branch (use Rails pluralization in the locale
file so %{count} and plural forms are handled), and
t('akahu_item.sync_status.partial', linked: linked_count, unlinked:
unlinked_count) for the else branch; update the corresponding locale keys
(akahu_item.sync_status.*) to include pluralization and interpolation, and make
the same change for the similar logic referenced around lines 124-133.

In `@app/models/akahu_item/importer.rb`:
- Around line 94-109: In fetch_pending_transactions_by_account, broaden the
exception handling so non-Akahu errors (at least JSON::ParserError and a generic
StandardError) are rescued and the method still returns the consistent shape {
success: false, by_account: Hash.new { |h,k| h[k] = [] }, error: e.message };
keep the existing Provider::Akahu::AkahuError rescue or replace it with a
multi-rescue clause, log the actual exception message (e.message) and class, and
ensure the by_account default uses the same Hash.new { |hash, key| hash[key] =
[] } construction used elsewhere.
- Around line 169-174: The current filtering in new_posted_transactions only
checks existing_posted_keys and allows duplicates within the same
posted_transactions batch; change the logic in the block that builds
new_posted_transactions (referencing posted_transactions,
transaction_storage_key, new_posted_transactions) to maintain a mutable
seen_keys set initialized from existing_posted_keys, and for each tx compute
key, skip if key.blank? or already in seen_keys, otherwise add the key to
seen_keys and accept the tx so duplicates in the same fetch are filtered out.

In `@app/models/family/akahu_connectable.rb`:
- Around line 12-15: The method create_akahu_item! currently hard-codes the
user-facing name "Akahu Connection"; update akahu_items.create! inside
create_akahu_item! to use I18n translation instead (e.g.,
I18n.t('<feature_namespace>.create_akahu_item.default_name')) and add the
corresponding key to your locale files organized hierarchically by feature
(e.g., family.akahu.create_akahu_item.default_name = "Akahu Connection") so
translations can be provided and maintained.

In `@app/models/provider/akahu_adapter.rb`:
- Around line 16-17: The provider card strings are hardcoded in the Akahu
adapter (the name and description entries) and must be replaced with i18n
lookups using the t() helper (e.g., use t('providers.akahu.name') and
t('providers.akahu.description') where the current name/description are set) and
add corresponding keys under a hierarchical locale namespace (providers -> akahu
-> name/description) in the locale YAML so translations load properly; update
any tests or fixtures that assert the literal strings to use the locale keys or
translated text.

In `@app/models/provider/akahu.rb`:
- Around line 12-19: The initialize method assigns `@base_url` directly which
risks SSRF; validate and normalize base_url (in initialize and any other places
that set `@base_url`) by parsing via URI, ensuring scheme is http or https and the
host exactly matches a trusted whitelist or falls under a trusted hostname
pattern, and if validation fails fallback to DEFAULT_BASE_URL or raise
AkahuError (e.g., use URI.parse(base_url.to_s.strip) and check
uri.scheme.in?(%w[http https]) and TRUSTED_HOSTS.include?(uri.host)); update the
`@base_url` assignment to use the validated value and raise
AkahuError.new("Invalid Akahu base_url", :configuration_error) if an explicit
invalid value is provided.
- Around line 164-177: The code currently includes raw provider payloads via
response.body in raised AkahuError messages and Rails.logger.error; remove
direct exposure of response.body and replace it with non-sensitive diagnostics
(e.g. response.code, response.message, and a short sanitized indicator or
truncated length/hash) when constructing errors or logs. Update the AkahuError
raises in the error handling block (references: AkahuError, response.body,
response.code, response.message, Rails.logger.error) to omit response.body, log
only minimal metadata, and if you need a trace for debugging produce a
sanitized/truncated or hashed version of the body instead of the raw payload.

In `@app/views/settings/providers/_akahu_panel.html.erb`:
- Line 6: The view is hardcoding the link label "Akahu" inside the link_to call;
replace that literal with a translation lookup so the panel is fully localized.
Update the call used within t("settings.providers.akahu_panel.step_1_html",
link: link_to(...)) to pass link_to(t("settings.providers.akahu") /* or an
appropriate i18n key with a default */, "https://my.akahu.nz/developers",
target: "_blank", rel: "noopener noreferrer", class: "text-primary font-medium
underline") so the link text comes from i18n rather than a hardcoded string.

In `@db/migrate/20260522120000_create_akahu_items_and_accounts.rb`:
- Line 11: The akahu_items.status column currently has a default but allows
NULL; update the migration so the schema enforces non-nullability by declaring
the column with null: false (e.g., in the create_table block for akahu_items
change the t.string :status line to include null: false) so the database
prevents NULLs for status and preserves sync/state integrity.

In `@db/schema.rb`:
- Line 1409: The unique index
idx_recurring_txns_on_family_merchant_amount_currency is too broad and blocks
valid recurring rows across different accounts; update the index definition to
scope uniqueness by account (e.g., include account_id in the indexed columns) or
remove the uniqueness constraint if per-account uniqueness isn't required —
locate the index named idx_recurring_txns_on_family_merchant_amount_currency and
change its columns to include account_id (or convert it to a non-unique index)
so duplicates are only prevented within the same account.

---

Nitpick comments:
In `@test/models/provider/metadata_test.rb`:
- Around line 8-12: The test "akahu is the only provider with multiple kinds"
hard-codes exclusivity; update it to assert that :akahu is present among
providers with multiple kinds instead of being the sole one. Modify the
assertion against Provider::Metadata::REGISTRY (the
providers_with_multiple_kinds calculation) to use an inclusion assertion (e.g.,
assert_includes providers_with_multiple_kinds.keys, :akahu) and, if desired,
also assert that providers_with_multiple_kinds is not empty to preserve the
intent that multi-kind providers exist.
🪄 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: b0ac9059-cbfd-4903-8077-f3ccf15cea87

📥 Commits

Reviewing files that changed from the base of the PR and between ced133d and d67dcee.

📒 Files selected for processing (53)
  • app/components/settings/provider_card.rb
  • app/controllers/accounts_controller.rb
  • app/controllers/akahu_items_controller.rb
  • app/controllers/settings/providers_controller.rb
  • app/helpers/settings_helper.rb
  • app/javascript/controllers/providers_filter_controller.js
  • app/models/account/provider_import_adapter.rb
  • app/models/akahu_account.rb
  • app/models/akahu_account/processor.rb
  • app/models/akahu_account/transactions/processor.rb
  • app/models/akahu_entry/processor.rb
  • app/models/akahu_item.rb
  • app/models/akahu_item/importer.rb
  • app/models/akahu_item/provided.rb
  • app/models/akahu_item/sync_complete_event.rb
  • app/models/akahu_item/syncer.rb
  • app/models/akahu_item/unlinking.rb
  • app/models/data_enrichment.rb
  • app/models/entry_search.rb
  • app/models/family.rb
  • app/models/family/akahu_connectable.rb
  • app/models/family/syncer.rb
  • app/models/provider/akahu.rb
  • app/models/provider/akahu_adapter.rb
  • app/models/provider/metadata.rb
  • app/models/provider_merchant.rb
  • app/models/transaction.rb
  • app/models/transaction/search.rb
  • app/models/user.rb
  • app/views/accounts/index.html.erb
  • app/views/akahu_items/_akahu_item.html.erb
  • app/views/akahu_items/select_accounts.html.erb
  • app/views/akahu_items/select_existing_account.html.erb
  • app/views/akahu_items/setup_accounts.html.erb
  • app/views/settings/providers/_akahu_panel.html.erb
  • app/views/settings/providers/show.html.erb
  • config/locales/views/akahu_items/en.yml
  • config/locales/views/settings/en.yml
  • config/routes.rb
  • db/migrate/20260522120000_create_akahu_items_and_accounts.rb
  • db/schema.rb
  • test/components/settings/provider_card_test.rb
  • test/controllers/akahu_items_controller_test.rb
  • test/controllers/settings/providers_controller_test.rb
  • test/models/akahu_account/processor_test.rb
  • test/models/akahu_account_test.rb
  • test/models/akahu_entry/processor_test.rb
  • test/models/akahu_item/importer_test.rb
  • test/models/akahu_item/syncer_test.rb
  • test/models/family/syncer_test.rb
  • test/models/provider/akahu_adapter_test.rb
  • test/models/provider/akahu_test.rb
  • test/models/provider/metadata_test.rb

Comment thread app/controllers/akahu_items_controller.rb
Comment thread app/controllers/akahu_items_controller.rb
Comment thread app/models/akahu_account.rb
Comment thread app/models/akahu_account.rb Outdated
Comment thread app/models/akahu_item.rb
Comment thread app/models/provider/akahu.rb Outdated
Comment thread app/models/provider/akahu.rb Outdated
Comment thread app/views/settings/providers/_akahu_panel.html.erb Outdated
Comment thread db/migrate/20260522120000_create_akahu_items_and_accounts.rb Outdated
Comment thread db/schema.rb Outdated
@superagent-security superagent-security Bot added pr:verified PR passed security analysis. and removed pr:flagged PR flagged for review by security analysis. labels May 22, 2026
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: 1

♻️ Duplicate comments (1)
app/models/family/akahu_connectable.rb (1)

12-16: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Treat blank item_name the same as missing.

|| preserves "", so callers that pass a blank string still skip the translated default and raise on the model validation instead.

Suggested fix
     akahu_item = akahu_items.create!(
-      name: item_name || I18n.t("family.akahu.create_akahu_item.default_name"),
+      name: item_name.presence || I18n.t("family.akahu.create_akahu_item.default_name"),
       app_token: app_token,
       user_token: user_token
     )
🤖 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 `@app/models/family/akahu_connectable.rb` around lines 12 - 16, The current
create_akahu_item! method treats a blank string as a provided name because it
uses ||, so update the name assignment to treat blank strings as missing by
using Rails presence (e.g., use item_name.presence or equivalent) when building
the akahu_items.create! call in create_akahu_item! so the
I18n.t("family.akahu.create_akahu_item.default_name") is used for nil or blank
names; leave the rest of the method and akahu_items.create! call unchanged.
🤖 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 `@app/controllers/akahu_items_controller.rb`:
- Around line 48-50: In the rescue block in the destroy action (the rescue => e
handling around Akahu unlink), stop exposing e.message in the user-facing flash:
keep the detailed info in the server log via Rails.logger.warn("Akahu unlink
during destroy failed: #{e.class} - #{e.message}") but change the redirect_to
call to use a generic/localized alert (e.g. t(".unlink_failed") without the
error interpolation) or a dedicated generic key, so no exception class/message
is rendered to users.

---

Duplicate comments:
In `@app/models/family/akahu_connectable.rb`:
- Around line 12-16: The current create_akahu_item! method treats a blank string
as a provided name because it uses ||, so update the name assignment to treat
blank strings as missing by using Rails presence (e.g., use item_name.presence
or equivalent) when building the akahu_items.create! call in create_akahu_item!
so the I18n.t("family.akahu.create_akahu_item.default_name") is used for nil or
blank names; leave the rest of the method and akahu_items.create! call
unchanged.
🪄 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: f48a5b2b-1ec4-4661-b938-67a0695e8302

📥 Commits

Reviewing files that changed from the base of the PR and between d67dcee and af7b448.

📒 Files selected for processing (15)
  • app/controllers/akahu_items_controller.rb
  • app/models/akahu_account.rb
  • app/models/akahu_item.rb
  • app/models/akahu_item/importer.rb
  • app/models/akahu_item/provided.rb
  • app/models/family/akahu_connectable.rb
  • app/models/provider/akahu.rb
  • app/models/provider/akahu_adapter.rb
  • app/models/provider_connection_status.rb
  • app/views/settings/providers/_akahu_panel.html.erb
  • config/locales/views/akahu_items/en.yml
  • db/migrate/20260522120000_create_akahu_items_and_accounts.rb
  • db/migrate/20260522121000_remove_legacy_recurring_transactions_family_unique_index.rb
  • db/schema.rb
  • test/controllers/akahu_items_controller_test.rb
✅ Files skipped from review due to trivial changes (1)
  • config/locales/views/akahu_items/en.yml

Comment thread app/controllers/akahu_items_controller.rb Outdated
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.

🧹 Nitpick comments (1)
app/controllers/akahu_items_controller.rb (1)

227-229: 💤 Low value

Consider the same pattern here for consistency.

Line 229 interpolates e.message into the user-facing alert, similar to what was just fixed in the destroy action. While ActiveRecord validation messages are often user-appropriate, they can sometimes expose model/attribute names that aren't user-friendly.

Suggested fix
 rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
   Rails.logger.error("Akahu account setup failed: #{e.class} - #{e.message}")
-  redirect_to accounts_path, alert: t(".creation_failed", error: e.message), status: :see_other
+  redirect_to accounts_path, alert: t(".creation_failed"), status: :see_other
 end
🤖 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 `@app/controllers/akahu_items_controller.rb` around lines 227 - 229, The rescue
block in AkahuItemsController that currently does rescue
ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e and redirects
with alert: t(".creation_failed", error: e.message) should be changed to log the
full error (include e.class and e.backtrace) via Rails.logger.error and stop
interpolating raw e.message into the user-facing alert; instead pass a
generic/sanitized I18n message (e.g. t(".creation_failed") or
t(".creation_failed", error: some_sanitized_text)) so users see a friendly
message while developers get the detailed error in the logs—update the rescue in
the create/account-setup flow (the rescue block in AkahuItemsController)
accordingly.
🤖 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.

Nitpick comments:
In `@app/controllers/akahu_items_controller.rb`:
- Around line 227-229: The rescue block in AkahuItemsController that currently
does rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e and
redirects with alert: t(".creation_failed", error: e.message) should be changed
to log the full error (include e.class and e.backtrace) via Rails.logger.error
and stop interpolating raw e.message into the user-facing alert; instead pass a
generic/sanitized I18n message (e.g. t(".creation_failed") or
t(".creation_failed", error: some_sanitized_text)) so users see a friendly
message while developers get the detailed error in the logs—update the rescue in
the create/account-setup flow (the rescue block in AkahuItemsController)
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2825b34f-b8f6-46da-8e3e-5c1704599978

📥 Commits

Reviewing files that changed from the base of the PR and between af7b448 and 9c6c666.

📒 Files selected for processing (2)
  • app/controllers/akahu_items_controller.rb
  • config/locales/views/akahu_items/en.yml
✅ Files skipped from review due to trivial changes (1)
  • config/locales/views/akahu_items/en.yml

@JSONbored
Copy link
Copy Markdown
Contributor

@failing @jjmata hi there, some suggestions for this PR after running an analysis from my end:

Concrete blockers:

  • app/controllers/akahu_items_controller.rb:98, :123, :171 build redirects from return/path params. Restrict to relative app paths or named routes, and reject external URLs, scheme-relative URLs, hosts, and schemes.
  • app/controllers/akahu_items_controller.rb:229, :270, :273, app/models/akahu_account/processor.rb:45, app/models/akahu_item.rb:56, :72, app/models/akahu_item/importer.rb:108, :154, and app/models/akahu_item/syncer.rb:62 expose raw exception messages to users/clients. Return stable generic errors and log details server-side only.
  • app/models/akahu_account/transactions/processor.rb:10, app/models/akahu_entry/processor.rb:163, :183, app/models/akahu_item/importer.rb:49, :156, and app/models/provider/akahu.rb:174, :191 log provider payload/error details. Redact or remove logs that can include tokens, account data, transaction payloads, or provider response content.
  • app/models/provider/akahu.rb introduces an HTTParty provider client. Add base URL hardening: fixed/allowlisted scheme + host, no loopback/private/internal hosts if any URL is configurable, and tests for rejected hosts.
  • app/views/settings/providers/_akahu_panel.html.erb:64, :70, :114, :120 render token/secret inputs as text fields. Use password/secret-field behavior and placeholder-only “leave blank to keep” semantics.
  • app/models/akahu_item/unlinking.rb:21 uses a broad update_all. Prove the relation is scoped to the current family/account/provider link and add a regression test showing another family’s holdings are untouched.
  • Replace native <details>/<summary> in app/views/akahu_items/_akahu_item.html.erb and app/views/settings/providers/_akahu_panel.html.erb with DS::Disclosure.

Suggested validation:

  • Add tests for external redirect rejection.
  • Add provider client tests for allowed/rejected hosts.
  • Add error-path tests proving raw exception messages are not returned to users.
  • Add scoped unlinking tests across two families.

It's also quite a large PR (55 files changed) - I've personally submitted a few large ones myself, so I understand the approach of trying to keep complete featuresets together, but it can make review a lot harder - in the future, I suggest breaking apart into smaller, more palatable bites.

Happy to help answer any questions or thoughts - cheers 👍🏻

@failing
Copy link
Copy Markdown
Author

failing commented May 23, 2026

@failing @jjmata hi there, some suggestions for this PR after running an analysis from my end:

Concrete blockers:

  • app/controllers/akahu_items_controller.rb:98, :123, :171 build redirects from return/path params. Restrict to relative app paths or named routes, and reject external URLs, scheme-relative URLs, hosts, and schemes.
  • app/controllers/akahu_items_controller.rb:229, :270, :273, app/models/akahu_account/processor.rb:45, app/models/akahu_item.rb:56, :72, app/models/akahu_item/importer.rb:108, :154, and app/models/akahu_item/syncer.rb:62 expose raw exception messages to users/clients. Return stable generic errors and log details server-side only.
  • app/models/akahu_account/transactions/processor.rb:10, app/models/akahu_entry/processor.rb:163, :183, app/models/akahu_item/importer.rb:49, :156, and app/models/provider/akahu.rb:174, :191 log provider payload/error details. Redact or remove logs that can include tokens, account data, transaction payloads, or provider response content.
  • app/models/provider/akahu.rb introduces an HTTParty provider client. Add base URL hardening: fixed/allowlisted scheme + host, no loopback/private/internal hosts if any URL is configurable, and tests for rejected hosts.
  • app/views/settings/providers/_akahu_panel.html.erb:64, :70, :114, :120 render token/secret inputs as text fields. Use password/secret-field behavior and placeholder-only “leave blank to keep” semantics.
  • app/models/akahu_item/unlinking.rb:21 uses a broad update_all. Prove the relation is scoped to the current family/account/provider link and add a regression test showing another family’s holdings are untouched.
  • Replace native <details>/<summary> in app/views/akahu_items/_akahu_item.html.erb and app/views/settings/providers/_akahu_panel.html.erb with DS::Disclosure.

Suggested validation:

  • Add tests for external redirect rejection.
  • Add provider client tests for allowed/rejected hosts.
  • Add error-path tests proving raw exception messages are not returned to users.
  • Add scoped unlinking tests across two families.

It's also quite a large PR (55 files changed) - I've personally submitted a few large ones myself, so I understand the approach of trying to keep complete featuresets together, but it can make review a lot harder - in the future, I suggest breaking apart into smaller, more palatable bites.

Happy to help answer any questions or thoughts - cheers 👍🏻

Thanks for taking a look!

  • Addressed
  • Addressed
  • Addressed
  • Not sure what you mean by this - the base url is hard coded (was originally configurable but that was changed after the auto feedback)
  • The input types are already set to password.
  • Addressed
  • Addressed

Added the relevant tests. Acknowledge it's quite a big PR. Will take on your feedback for splitting it up next time. Cheers!

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

Caution

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

⚠️ Outside diff range comments (3)
app/controllers/akahu_items_controller.rb (1)

63-64: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

These flows still collapse multiple Akahu connections into one.

credentialed_akahu_item just grabs the first active credentialed item in the family, so preload_accounts, select_accounts, and link_accounts can operate on the wrong connection as soon as a family has more than one Akahu item. These endpoints need an explicit akahu_item_id and should load that specific record from Current.family.

Also applies to: 73-74, 90-90, 249-250

🤖 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 `@app/controllers/akahu_items_controller.rb` around lines 63 - 64, The
endpoints are incorrectly using credentialed_akahu_item (which returns the first
active item) causing multiple Akahu connections to collapse; change these flows
(preload_accounts, select_accounts, link_accounts and the occurrences at the
other mentioned locations) to require an akahu_item_id param and load the
specific AkahuItem from Current.family (e.g.
Current.family.akahu_items.find_by!(id: params[:akahu_item_id])) instead of
calling credentialed_akahu_item, validate existence and credentials on that
loaded record, and use that instance for all subsequent operations and
responses.
app/models/akahu_entry/processor.rb (1)

121-129: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the note labels written into transaction notes.

These prefixes are persisted and surfaced back to users, so they should not be hard-coded English strings.

As per coding guidelines, "Always use t() helper for user-facing strings."

🤖 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 `@app/models/akahu_entry/processor.rb` around lines 121 - 129, The notes method
currently hard-codes English prefixes ("Reference:", "Particulars:", "Code:",
"Other account:"); change those to use Rails i18n by replacing each literal with
t(...) calls (e.g. t('akahu_entry.notes.reference'),
t('akahu_entry.notes.particulars'), t('akahu_entry.notes.code'),
t('akahu_entry.notes.other_account')) inside the parts array in the notes
method, leaving data/meta interpolation unchanged, and add corresponding keys to
the locale YAML so translations are available; keep the existing presence/join
logic and ensure you call t() with appropriate scope/namespace matching other
akahu_entry translations.
app/models/akahu_item.rb (1)

49-50: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sanitize failed processor results before returning them.

This path still forwards data[:error]/errors and the full result hash back to callers. If a processor packs raw provider payloads or secret-bearing messages into its failure result, that bypasses the generic exception handling you added in the rescue branch. Return a generic translated error here and keep the detailed payload server-side only.

Also applies to: 140-150

🤖 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 `@app/models/akahu_item.rb` around lines 49 - 50, The branch in AkahuItem that
returns raw failure payloads (the Hash path checking
result.with_indifferent_access[:success] == false) must not forward result or
provider error fields; instead return only akahu_account_id, success: false and
a generic translated error message (e.g. via I18n.t or a dedicated processor
failure message) and remove or omit the full result/errors field. Update the
same pattern in the other failing-return block referenced (around lines 140–150)
so both places sanitize processor results and keep detailed payloads server-side
only.
♻️ Duplicate comments (1)
app/models/akahu_item/unlinking.rb (1)

21-34: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t swallow unlink failures here.

AkahuItemsController#destroy only stops destroying the item when unlink_all! raises, but this rescue converts every unlink error into result[:error] and keeps going. That means partial unlink failures still end in destroy_later plus a success notice, leaving cleanup incomplete.

🤖 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 `@app/models/akahu_item/unlinking.rb` around lines 21 - 34, The current rescue
in the unlinking logic swallows errors so AkahuItemsController#destroy never
sees failures; change the handler so unlink failures re-raise instead of
silently continuing: in the method that runs the ActiveRecord::Base.transaction
/ links.each loop (the unlink_all! logic in unlinking.rb), keep the logging of
the exception (Rails.logger.warn with provider_account.id and link_ids) but
after setting result[:error] either raise the original exception (raise e) or
remove the rescue entirely so the error bubbles to AkahuItemsController#destroy;
this ensures unlink_all! failures stop destruction and are handled by the
controller.
🤖 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 `@app/models/akahu_account/processor.rb`:
- Around line 45-46: The current log in AkahuAccount::Processor includes
e.message which can leak provider details; change the Rails.logger.error call to
log only stable metadata (e.g., akahu_account.id and the exception class) and
remove e.message, and separately send the full exception to your error tracker
(e.g., call Sentry.capture_exception(e, extra: { akahu_account_id:
akahu_account.id }) or Raven.capture_exception with the akahu_account id) so raw
details go to Sentry only while logs contain non-sensitive context.

In `@app/models/akahu_item/syncer.rb`:
- Around line 62-63: Remove the raw exception text from the syncer logs: in
AkahuItem::Syncer replace the Rails.logger.error interpolation that includes
e.message with a generic message (e.g., "AkahuItem::Syncer - Unexpected sync
error: #{e.class}") and rely on your error-tracking (capture_exception) for full
details; keep the collect_health_stats(sync, errors: [...]) call as-is so
monitoring records the failure without exposing provider payloads.

---

Outside diff comments:
In `@app/controllers/akahu_items_controller.rb`:
- Around line 63-64: The endpoints are incorrectly using credentialed_akahu_item
(which returns the first active item) causing multiple Akahu connections to
collapse; change these flows (preload_accounts, select_accounts, link_accounts
and the occurrences at the other mentioned locations) to require an
akahu_item_id param and load the specific AkahuItem from Current.family (e.g.
Current.family.akahu_items.find_by!(id: params[:akahu_item_id])) instead of
calling credentialed_akahu_item, validate existence and credentials on that
loaded record, and use that instance for all subsequent operations and
responses.

In `@app/models/akahu_entry/processor.rb`:
- Around line 121-129: The notes method currently hard-codes English prefixes
("Reference:", "Particulars:", "Code:", "Other account:"); change those to use
Rails i18n by replacing each literal with t(...) calls (e.g.
t('akahu_entry.notes.reference'), t('akahu_entry.notes.particulars'),
t('akahu_entry.notes.code'), t('akahu_entry.notes.other_account')) inside the
parts array in the notes method, leaving data/meta interpolation unchanged, and
add corresponding keys to the locale YAML so translations are available; keep
the existing presence/join logic and ensure you call t() with appropriate
scope/namespace matching other akahu_entry translations.

In `@app/models/akahu_item.rb`:
- Around line 49-50: The branch in AkahuItem that returns raw failure payloads
(the Hash path checking result.with_indifferent_access[:success] == false) must
not forward result or provider error fields; instead return only
akahu_account_id, success: false and a generic translated error message (e.g.
via I18n.t or a dedicated processor failure message) and remove or omit the full
result/errors field. Update the same pattern in the other failing-return block
referenced (around lines 140–150) so both places sanitize processor results and
keep detailed payloads server-side only.

---

Duplicate comments:
In `@app/models/akahu_item/unlinking.rb`:
- Around line 21-34: The current rescue in the unlinking logic swallows errors
so AkahuItemsController#destroy never sees failures; change the handler so
unlink failures re-raise instead of silently continuing: in the method that runs
the ActiveRecord::Base.transaction / links.each loop (the unlink_all! logic in
unlinking.rb), keep the logging of the exception (Rails.logger.warn with
provider_account.id and link_ids) but after setting result[:error] either raise
the original exception (raise e) or remove the rescue entirely so the error
bubbles to AkahuItemsController#destroy; this ensures unlink_all! failures stop
destruction and are handled by the controller.
🪄 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: 1ba2242e-498e-4b6c-bb9b-ea7108edb8fe

📥 Commits

Reviewing files that changed from the base of the PR and between 9c6c666 and 733d7d9.

📒 Files selected for processing (16)
  • app/controllers/akahu_items_controller.rb
  • app/models/akahu_account/processor.rb
  • app/models/akahu_account/transactions/processor.rb
  • app/models/akahu_entry/processor.rb
  • app/models/akahu_item.rb
  • app/models/akahu_item/importer.rb
  • app/models/akahu_item/syncer.rb
  • app/models/akahu_item/unlinking.rb
  • app/models/provider/akahu.rb
  • app/views/akahu_items/_akahu_item.html.erb
  • app/views/settings/providers/_akahu_panel.html.erb
  • config/locales/views/akahu_items/en.yml
  • test/controllers/akahu_items_controller_test.rb
  • test/models/akahu_account_test.rb
  • test/models/akahu_item_importer_error_messages_test.rb
  • test/models/akahu_item_unlinking_test.rb
✅ Files skipped from review due to trivial changes (1)
  • config/locales/views/akahu_items/en.yml

Comment thread app/models/akahu_account/processor.rb Outdated
Comment thread app/models/akahu_item/syncer.rb Outdated
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: 1

Caution

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

⚠️ Outside diff range comments (1)
app/models/akahu_item.rb (1)

47-56: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep raw processor messages out of the logs too.

The returned hash is now sanitized, but Line 55 still logs e.message. In the same failure cases these tests model, that message can contain tokens or account numbers, so the sensitive data still lands in server logs. Log stable context only (AkahuItem/account ids + exception class) and keep the raw message out of normal logs.

🤖 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 `@app/models/akahu_item.rb` around lines 47 - 56, The rescue block inside
AkahuItem's akahu_accounts processing currently logs e.message which may contain
sensitive tokens; update the rescue in the AkahuItem loop (where
AkahuAccount::Processor.new(...).process is called) to log only stable context
(AkahuItem id, akahu_account.id) and the exception class (e.class) — remove
e.message from the Rails.logger.error call — and keep the returned sanitized
hash unchanged.
🧹 Nitpick comments (1)
test/controllers/akahu_items_controller_test.rb (1)

112-139: ⚡ Quick win

Add external return_to rejection coverage for link_accounts.

This test validates connection selection correctly, but link_accounts also receives return_to from the setup flow. Add one regression asserting absolute external URLs (for example, https://evil.example/accounts) are rejected and redirect falls back to accounts_path.

Proposed test addition
+  test "link accounts rejects absolute external return paths" do
+    second_item = AkahuItem.create!(
+      family: `@family`,
+      name: "Secondary Akahu",
+      app_token: "second-app-token",
+      user_token: "second-user-token"
+    )
+    second_account = second_item.akahu_accounts.create!(
+      name: "Secondary Checking",
+      account_id: "acc_secondary",
+      currency: "NZD"
+    )
+
+    post link_accounts_akahu_items_url, params: {
+      akahu_item_id: second_item.id,
+      account_ids: [ second_account.id ],
+      accountable_type: "Depository",
+      return_to: "https://evil.example/accounts"
+    }
+
+    assert_redirected_to accounts_path
+  end

As per coding guidelines: "Write minimal, effective tests. Only test critical and important code paths."

🤖 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 `@test/controllers/akahu_items_controller_test.rb` around lines 112 - 139,
Update the "link accounts uses requested Akahu connection" test to also pass a
malicious absolute external return_to (e.g. "https://evil.example/accounts") in
the post to link_accounts_akahu_items_url and assert that the controller rejects
it by redirecting to accounts_path instead of the external URL; keep the
existing assertions that one Account and one AccountProvider are created and
that AccountProvider.order(:created_at).last.provider_id equals
second_account.id, and assert that `@akahu_account.reload.current_account` remains
nil to cover the open-redirect fallback behavior.
🤖 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 `@test/models/akahu_account_test.rb`:
- Around line 76-80: The failure stub for
AkahuAccount::Processor.any_instance.stubs(:process) currently omits a :result
key so the test's refute result.key?(:result) is vacuous; update the stubbed
return to include a representative sanitized result payload (e.g., a hash with
the expected keys/structure used by process_accounts) under the :result key so
the sanitization/stripping behavior is exercised—modify the return in the
AkahuAccount::Processor.any_instance.stubs(:process) call to include :result =>
{ ...representative sanitized fields... } alongside success: false, error:
raw_message, errors: [...].

---

Outside diff comments:
In `@app/models/akahu_item.rb`:
- Around line 47-56: The rescue block inside AkahuItem's akahu_accounts
processing currently logs e.message which may contain sensitive tokens; update
the rescue in the AkahuItem loop (where AkahuAccount::Processor.new(...).process
is called) to log only stable context (AkahuItem id, akahu_account.id) and the
exception class (e.class) — remove e.message from the Rails.logger.error call —
and keep the returned sanitized hash unchanged.

---

Nitpick comments:
In `@test/controllers/akahu_items_controller_test.rb`:
- Around line 112-139: Update the "link accounts uses requested Akahu
connection" test to also pass a malicious absolute external return_to (e.g.
"https://evil.example/accounts") in the post to link_accounts_akahu_items_url
and assert that the controller rejects it by redirecting to accounts_path
instead of the external URL; keep the existing assertions that one Account and
one AccountProvider are created and that
AccountProvider.order(:created_at).last.provider_id equals second_account.id,
and assert that `@akahu_account.reload.current_account` remains nil to cover the
open-redirect fallback behavior.
🪄 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: 3fec8495-adc8-4967-bef7-7d19382894b8

📥 Commits

Reviewing files that changed from the base of the PR and between 733d7d9 and cb02ecf.

📒 Files selected for processing (14)
  • app/controllers/akahu_items_controller.rb
  • app/models/akahu_account/processor.rb
  • app/models/akahu_entry/processor.rb
  • app/models/akahu_item.rb
  • app/models/akahu_item/syncer.rb
  • app/models/provider/akahu_adapter.rb
  • app/views/akahu_items/select_accounts.html.erb
  • config/locales/views/akahu_items/en.yml
  • test/controllers/akahu_items_controller_test.rb
  • test/models/akahu_account/processor_test.rb
  • test/models/akahu_account_test.rb
  • test/models/akahu_entry/processor_test.rb
  • test/models/akahu_item/syncer_test.rb
  • test/models/provider/akahu_adapter_test.rb
✅ Files skipped from review due to trivial changes (1)
  • config/locales/views/akahu_items/en.yml

Comment thread test/models/akahu_account_test.rb
@superagent-security superagent-security Bot added pr:flagged PR flagged for review by security analysis. and removed pr:verified PR passed security analysis. labels May 23, 2026
Copy link
Copy Markdown

@superagent-security superagent-security Bot left a comment

Choose a reason for hiding this comment

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

Superagent found 2 security concern(s).

Comment thread test/models/akahu_account/processor_test.rb
Comment thread test/models/akahu_item/syncer_test.rb
Comment thread test/models/akahu_account/processor_test.rb
Comment thread test/models/akahu_item/syncer_test.rb
@superagent-security superagent-security Bot added pr:verified PR passed security analysis. and removed pr:flagged PR flagged for review by security analysis. labels May 24, 2026
@jjmata jjmata removed the pr:verified PR passed security analysis. label May 24, 2026
@superagent-security superagent-security Bot added the pr:verified PR passed security analysis. label May 24, 2026
@jjmata jjmata self-requested a review May 24, 2026 11:07
@superagent-security superagent-security Bot added pr:flagged PR flagged for review by security analysis. and removed pr:verified PR passed security analysis. labels May 24, 2026
Copy link
Copy Markdown

@superagent-security superagent-security Bot left a comment

Choose a reason for hiding this comment

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

Superagent found 2 security concern(s).

Copy link
Copy Markdown

@superagent-security superagent-security Bot left a comment

Choose a reason for hiding this comment

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

Superagent found 2 security concern(s).

Copy link
Copy Markdown

DS Drift Patrol — 1 finding

Scanned 2026-05-25 · diff-scope only (added + lines)


Rule 3 — DS::Tooltip a11y regression (#1845): missing as: :span

app/views/akahu_items/_akahu_item.html.erb

+  <%= render DS::Tooltip.new(text: akahu_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive") %>

This tooltip sits inline next to text/span content inside a list row. Per the a11y fix landed in #1845, any DS::Tooltip rendered inline alongside text must include as: :span so the trigger renders as an inline element rather than a block-level <div>, preventing layout reflow.

➜ Add as: :span:

<%= render DS::Tooltip.new(text: akahu_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %>

Generated by Claude Code


Generated by Claude Code

Copy link
Copy Markdown

@superagent-security superagent-security Bot left a comment

Choose a reason for hiding this comment

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

Superagent found 1 security concern(s).

t.jsonb :raw_payload
t.jsonb :raw_institution_payload
t.text :app_token
t.text :user_token
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Akahu API tokens are added as plaintext database columns

Akahu app/user tokens are stored in plain text columns.

Encrypt Akahu token fields and ensure they are never logged or rendered.

AI prompt
Check if this security scanner issue is valid. If so, understand the root cause and fix it. If appropriate, update or add tests. Keep the change focused and preserve intended behavior.

<file name="db/migrate/20260522120000_create_akahu_items_and_accounts.rb">
<violation number="1" location="db/migrate/20260522120000_create_akahu_items_and_accounts.rb:18">
<priority>P2</priority>
<title>Akahu API tokens are added as plaintext database columns</title>
<evidence>The migration creates `akahu_items.app_token` and `akahu_items.user_token` as plain `text` columns. These tokens are used as Akahu credentials for financial-account access, so a database dump or SQL read would expose reusable provider credentials.</evidence>
<recommendation>Store Akahu credentials with the app's encrypted credential mechanism (for example Active Record encryption or the same encrypted field pattern used for other provider secrets), rotate any tokens created during testing, and avoid returning or logging these fields anywhere.</recommendation>
</violation>
</file>

Copy link
Copy Markdown

Two blockers before this can move past pr:flagged:

1. Open redirect (security)
safe_return_to_path (used at lines 98, 123, 171 in akahu_items_controller.rb) must restrict to relative paths or a named-route allowlist. As written, an attacker can craft a link that redirects to an external URL after a legitimate OAuth callback. Use Rails' url_for with only_path: true, or validate against URI.parse(path).relative?. @JSONbored flagged the same issue — it needs a fix, not just acknowledgement.

2. DS Drift — DS::Tooltip missing as: :span
app/views/akahu_items/_akahu_item.html.erb renders DS::Tooltip without as: :span, which is required per rule #1845 for inline tooltip anchors. Fails the DS accessibility invariant and will be caught by the drift patrol on every future scan.


Generated by Claude Code

@superagent-security superagent-security Bot added pr:verified PR passed security analysis. and removed pr:flagged PR flagged for review by security analysis. labels May 27, 2026
Copy link
Copy Markdown

DS Drift Patrol — 2 findings

Rule 4 — DS::Disclosure summary indentation (#1855 review)

Two new DS::Disclosure(variant: :card) callsites open their with_summary_content block with a bare flex row at the top level, without a w-full wrapper. Because <summary> renders as display: list-item, the flex row will shrink-wrap rather than spanning the full disclosure header width.

File Snippet
app/views/settings/providers/_akahu_panel.html.erb <div class="flex items-center justify-between gap-2">
app/views/akahu_items/_akahu_item.html.erb <div class="flex items-center justify-between gap-2">

Fix: add w-full to the top-level flex container inside each with_summary_content block:

<div class="flex items-center justify-between gap-2 w-full">

Generated by Claude Code

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

Labels

contributor:verified Contributor passed trust analysis. pr:verified PR passed security analysis.

Development

Successfully merging this pull request may close these issues.

5 participants