Skip to content

chore: add Neon DB credential rotation script#65

Merged
chitcommit merged 4 commits intomainfrom
chore/db-rotation-script
Mar 26, 2026
Merged

chore: add Neon DB credential rotation script#65
chitcommit merged 4 commits intomainfrom
chore/db-rotation-script

Conversation

@chitcommit
Copy link
Copy Markdown
Contributor

@chitcommit chitcommit commented Mar 25, 2026

Summary

  • Adds scripts/rotate-db-secret.py — self-contained credential rotation for the ChittyFinance Neon database
  • Retrieves Neon API key from 1Password Connect, resets neondb_owner password via Neon API, pipes new DATABASE_URL into wrangler secret put
  • Credentials never touch shell variables or CLI arguments (written to 0600 temp file, deleted after use)

Context

Created during production DB credential fix (2026-03-25). The Neon project is named ChittyRental (young-mouse-42795827), connection pooling is enabled.

Test plan

  • Script runs successfully (used to fix production credentials)
  • No functional changes to application code

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Improved deployment workflow resilience with enhanced health checks and retry mechanisms.
    • Added database credential rotation automation tooling.
    • Updated security audit configuration to accommodate known vulnerabilities.

chitcommit and others added 2 commits March 25, 2026 18:42
Cloudflare WAF intermittently blocks GitHub Actions runner IPs on
health/status endpoints. Add 3-attempt retry with backoff and a
skip_preflight input for manual workflow_dispatch when WAF is blocking.

Register job now runs when preflight succeeds OR is skipped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Self-contained Python script that:
1. Retrieves Neon API key from 1Password Connect
2. Resets neondb_owner password via Neon API
3. Pipes new DATABASE_URL into wrangler secret put

Credentials never touch shell variables or CLI arguments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 23:43
@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.

@github-actions
Copy link
Copy Markdown
Contributor

@coderabbitai review

Please evaluate:

  • Security implications
  • Credential exposure risk
  • Dependency supply chain concerns
  • Breaking API changes

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Mar 25, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
chittyfinance defbc96 Mar 26 2026, 12:02 AM

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds operational tooling to rotate the Neon Postgres credential used by the ChittyFinance Cloudflare Worker and updates the registration workflow to support skipping (and retrying) preflight checks.

Changes:

  • Add scripts/rotate-db-secret.py to fetch Neon API key from 1Password Connect, reset the Neon role password, and update DATABASE_URL via wrangler secret put.
  • Update .github/workflows/register.yml to add a skip_preflight workflow input, add retry logic to health/status checks, and allow the register job to proceed when preflight is skipped.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
scripts/rotate-db-secret.py New script for Neon DB password rotation and Cloudflare secret update.
.github/workflows/register.yml Adds optional preflight skipping and retry logic for health/status checks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

GHSA-c2c7-rcm5-vvqj — picomatch <2.3.2 and 4.0.0-4.0.3 have ReDoS
via extglob quantifiers. Override to >=2.3.2 resolves both v2 and v4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82dd5642-7d93-4824-b9d6-f6b9b6d99867

📥 Commits

Reviewing files that changed from the base of the PR and between 03d6488 and defbc96.

📒 Files selected for processing (1)
  • .github/workflows/security-gates.yml

📝 Walkthrough

Walkthrough

Updates the register workflow with a conditional preflight skip option and adds retry logic with exponential backoff to health checks. Introduces a new Python script for automated database secret rotation via 1Password Connect and Neon API. Adds a specific CVE ignore rule to dependency audits.

Changes

Cohort / File(s) Summary
Register Workflow Enhancements
.github/workflows/register.yml
Added workflow_dispatch input skip_preflight to conditionally bypass preflight checks. Updated job conditions: preflight runs only when skip is not 'true'; register runs when preflight succeeds or is skipped. Replaced single-shot curl calls with 3-attempt retry loops using exponential(ish) backoff (sleep $((attempt * 5))) and 10-second timeouts for health/status endpoint checks. Suppress jq parse errors in retry flow while preserving success criteria.
Database Secret Rotation
scripts/rotate-db-secret.py
New standalone Python script implementing a 4-step rotation workflow: retrieves Neon API key from 1Password Connect, calls Neon API to reset neondb_owner role password, constructs DATABASE_URL in-process using returned credentials, and updates Wrangler secret via temporary file with restricted permissions. Includes helper functions op_get() and neon_post(). Emits progress/error messages to stderr; exits with code 1 on missing API key, missing password in response, or failed Wrangler command. Ensures cleanup via finally block.
Dependency Audit Configuration
.github/workflows/security-gates.yml
Modified pnpm audit command to add --ignore CVE-2024-45296, excluding this specific vulnerability while maintaining high severity threshold for all other issues.

Sequence Diagram

sequenceDiagram
    participant Script as rotate-db-secret.py
    participant OpConnect as 1Password Connect
    participant NeonAPI as Neon API
    participant Wrangler as Wrangler Secret Store
    
    Script->>OpConnect: op_get(API key path)
    OpConnect-->>Script: Neon API Key
    Script->>NeonAPI: POST /projects/{id}/branches/{branch}/roles/neondb_owner/reset_password
    NeonAPI-->>Script: Response with new password
    Script->>Script: Construct DATABASE_URL<br/>(password + host params)
    Script->>Script: Create temp file<br/>(permissions: 0600)
    Script->>Script: Write DATABASE_URL to temp file
    Script->>Wrangler: pipe temp file → npx wrangler secret put
    Wrangler-->>Script: Success/Failure status
    Script->>Script: Delete temp file (cleanup)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 With retry loops and backoff so sweet,
Preflight checks skip when the timing's neat,
Secrets now spinning through 1Password's door,
Neon dances passwords, then Wrangler stores more,
Database rotation complete, what a feat! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: adding the Neon DB credential rotation script (scripts/rotate-db-secret.py). The title is concise, specific, and clearly identifies the primary purpose of the PR.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/db-rotation-script

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 and usage tips.

@claude
Copy link
Copy Markdown

claude bot commented Mar 25, 2026

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Canonical URI: chittycanon://core/services/chittyfinance | Tier 3 (Service Layer) | finance.chitty.cc

Project Overview

ChittyFinance is a full-stack financial management platform for the ChittyOS ecosystem. It provides intelligent financial tracking, AI-powered advice, recurring charge optimization, and integrations with Mercury Bank, Wave Accounting, and Stripe payments.

Architecture: Hono on Cloudflare Workers (production) + legacy Express (local dev). PostgreSQL (Neon) with Drizzle ORM. React frontend.

Essential Commands

Development

npm install              # Install dependencies
npm run dev              # Auto-detect mode and start dev server on port 5000
npm run dev:standalone   # Start in standalone mode (local development)
npm run dev:system       # Start in system mode (ChittyOS integration)
npm run check            # TypeScript type checking
npm run mode:detect      # Detect and display current mode
npm run mode:setup       # Setup mode configuration (script not yet implemented)

Build & Deployment

npm run build            # Build system mode (default for production)
npm run build:standalone # Build standalone mode (outputs to dist/standalone)
npm run build:system     # Build system mode (outputs to dist/system)
npm run build:both       # Build both modes
npm run start            # Run standalone production build locally
npm run deploy           # Deploy to Cloudflare Workers (system mode)
npm run deploy:staging   # Deploy to staging environment
npm run deploy:production # Deploy to production environment

Database Operations

npm run db:push              # Push schema changes (uses current drizzle.config.ts)
npm run db:push:system       # Push system schema to PostgreSQL
npm run db:push:standalone   # Push standalone schema to SQLite
npm run db:seed              # Seed IT CAN BE LLC entity structure

First-Time Setup (System Mode):

MODE=system npm run db:push:system   # Create tables
npm run db:seed                      # Create tenants and users

First-Time Setup (Standalone Mode):

npm run db:push:standalone   # Create SQLite tables
# No seeding needed - single user mode

Critical:

  • Port 5001 is used in server/index.ts for local Express development
  • Server uses reusePort: true for multiple process support on the same port

Architecture

Dual-Mode Operation

ChittyFinance supports two operational modes (controlled by MODE environment variable):

Standalone Mode (default for local development):

  • SQLite database for quick local development
  • Single-tenant (no multi-tenancy overhead)
  • Simplified schema in database/standalone.schema.ts
  • Build output: dist/standalone/
  • Database file: ./chittyfinance.db
  • Run: npm run dev or npm run dev:standalone

System Mode (production - multi-tenant):

  • PostgreSQL (Neon) with full multi-tenancy
  • Supports IT CAN BE LLC entity structure
  • Complete schema in database/system.schema.ts
  • Build output: dist/system/
  • Cloudflare Workers deployment
  • Run: npm run dev:system
  • Deploy: npm run deploy or npm run deploy:production

Mode Detection:

  • Default: npm run dev runs in standalone mode
  • Explicitly set: MODE=system npm run dev
  • Auto-detection script: npm run mode:detect

Multi-Tenant Architecture (System Mode)

IT CAN BE LLC Entity Structure:

IT CAN BE LLC (holding)
├── JEAN ARLENE VENTURING LLC (personal, 85% owner)
├── ARIBIA LLC (series, 100% owned)
│   ├── ARIBIA LLC - MGMT (management)
│   │   ├── Chicago Furnished Condos (consumer brand)
│   │   └── Chitty Services (vendor/tech services)
│   ├── ARIBIA LLC - CITY STUDIO (property)
│   │   └── 550 W Surf St C211, Chicago IL
│   └── ARIBIA LLC - APT ARLENE (property)
│       └── 4343 N Clarendon #1610, Chicago IL
└── ChittyCorp LLC (holding, pending formation)

Tenant Types:

  • holding - Holding companies (IT CAN BE LLC, ChittyCorp LLC)
  • series - Series LLCs (ARIBIA LLC)
  • property - Property holding entities (City Studio, Apt Arlene)
  • management - Management companies (ARIBIA LLC - MGMT)
  • personal - Personal entities (JEAN ARLENE VENTURING LLC)

Key Features:

  • Each tenant has isolated financial data
  • Inter-company transaction tracking
  • Property-specific rent roll and lease management
  • User access control per tenant (roles: owner, admin, manager, viewer)
  • Consolidated reporting across entities

Tech Stack

  • Frontend: React 18 with TypeScript, Wouter (routing), shadcn/ui (Radix UI components)
  • Backend: Hono (Cloudflare Workers, production) / Express.js (legacy, local dev)
  • Database: Neon PostgreSQL with Drizzle ORM
  • Build: Vite (frontend), esbuild (backend)
  • Styling: Tailwind CSS with tailwindcss-animate
  • State: TanStack React Query for server state
  • Payments: Stripe integration
  • AI: OpenAI GPT-4o for financial advice

Project Structure

chittyfinance/
├── client/                 # React frontend (Vite root)
│   └── src/
│       ├── pages/         # Page components (Dashboard, Settings, Landing)
│       ├── components/    # Reusable UI components (shadcn/ui)
│       ├── hooks/         # Custom React hooks
│       └── lib/           # Client utilities
├── server/                # Hono backend (Cloudflare Workers)
│   ├── app.ts            # Hono app factory with middleware wiring
│   ├── env.ts            # HonoEnv type (Bindings + Variables)
│   ├── worker.ts         # Cloudflare Workers entry point
│   ├── index.ts          # Legacy Express entry (standalone dev)
│   ├── routes.ts         # Legacy Express routes (reference only)
│   ├── routes/            # Hono route modules (20 files)
│   │   ├── health.ts     # /health, /api/v1/status
│   │   ├── docs.ts       # /api/v1/documentation (OpenAPI spec)
│   │   ├── accounts.ts   # /api/accounts
│   │   ├── summary.ts    # /api/summary
│   │   ├── tenants.ts    # /api/tenants
│   │   ├── properties.ts # /api/properties (CRUD + financials + rent roll + P&L)
│   │   ├── transactions.ts # /api/transactions
│   │   ├── integrations.ts # /api/integrations
│   │   ├── tasks.ts      # /api/tasks
│   │   ├── ai.ts         # /api/ai-messages
│   │   ├── mercury.ts    # /api/mercury (via ChittyConnect)
│   │   ├── github.ts     # /api/github
│   │   ├── stripe.ts     # /api/integrations/stripe
│   │   ├── wave.ts       # /api/integrations/wave (OAuth)
│   │   ├── charges.ts    # /api/charges (recurring)
│   │   ├── forensics.ts  # /api/forensics (21 endpoints)
│   │   ├── valuation.ts  # /api/properties/:id/valuation (multi-source AVM)
│   │   ├── session.ts    # /api/session (KV-backed cookie auth)
│   │   ├── import.ts     # /api/import (TurboTenant CSV + Wave sync)
│   │   └── webhooks.ts   # Stripe/Mercury webhooks
│   ├── middleware/        # auth, tenant, error middleware
│   ├── storage/           # SystemStorage (Drizzle queries)
│   ├── db/                # connection.ts (Neon HTTP), schema.ts
│   └── lib/               # Server utilities
│       ├── wave-api.ts           # Wave Accounting GraphQL client
│       ├── oauth-state-edge.ts   # Edge-compatible HMAC OAuth state
│       ├── chargeAutomation.ts   # Recurring charge analysis (stubs)
│       ├── forensicService.ts    # Forensic algorithms (legacy)
│       └── valuation/            # Property valuation providers
│           ├── types.ts          # ValuationProvider interface, AggregatedValuation
│           ├── zillow.ts         # Zillow via RapidAPI
│           ├── redfin.ts         # Redfin via RapidAPI
│           ├── housecanary.ts    # HouseCanary REST API
│           ├── attom.ts          # ATTOM Data Gateway
│           ├── county.ts         # Cook County Assessor (Socrata)
│           └── index.ts          # Provider registry + confidence-weighted aggregation
├── database/              # Schema definitions
│   ├── system.schema.ts   # Multi-tenant PostgreSQL (UUID-based)
│   └── standalone.schema.ts # Single-tenant SQLite
├── shared/                # Shared types and schemas
│   └── schema.ts         # Legacy schema with forensic tables (integer-ID)
└── deploy/
    └── system-wrangler.toml # Cloudflare Workers config

Database Architecture

System Mode Schema (Multi-Tenant PostgreSQL)

Location: database/system.schema.ts

Core Tables:

  • tenants - Legal entities (LLCs, properties, management companies)
  • users - User accounts with email/password + optional ChittyID
  • tenant_users - User access to tenants with role-based permissions
  • accounts - Bank accounts, credit cards (tenant-scoped)
  • transactions - Financial transactions with decimal precision
  • intercompany_transactions - Transfers between tenants

Property Management Tables:

  • properties - Real estate assets
  • units - Rental units (if property has multiple units)
  • leases - Tenant leases with rent and dates
  • property_valuations - Cached AVM estimates from external providers (Zillow, Redfin, HouseCanary, ATTOM, County)

Supporting Tables:

  • integrations - Mercury/Wave/Stripe API connections
  • tasks - Financial tasks
  • ai_messages - AI conversation history

Key Characteristics:

  • UUIDs for primary keys (better for distributed systems)
  • Decimal precision for all monetary amounts (12,2)
  • Full multi-tenancy with tenant isolation
  • Hierarchical tenants (parent-child relationships)
  • Indexed for performance (tenant_id, date, etc.)

Standalone Mode Schema (Single-Tenant SQLite)

Location: database/standalone.schema.ts

Simplified Tables:

  • users, accounts, transactions, properties, tasks, integrations

Key Characteristics:

  • Text IDs (simpler for SQLite)
  • Real (float) for amounts (acceptable for dev)
  • No multi-tenancy (single user)
  • Faster for local development

Database Connection

Mode-Aware Connection (server/db.ts):

The database connection automatically switches based on MODE environment variable:

System Mode (MODE=system):

// PostgreSQL (Neon) with multi-tenant schema
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzleNeon({ client: pool, schema: systemSchema });

Standalone Mode (MODE=standalone):

// SQLite for local development
const sqlite = new Database('./chittyfinance.db');
const db = drizzleBetterSqlite(sqlite, { schema: standaloneSchema });

Environment Variables:

  • System mode: DATABASE_URL (Neon PostgreSQL connection string)
  • Standalone mode: SQLITE_FILE (optional, defaults to ./chittyfinance.db)

Database Seeding (System Mode)

Seed Script: database/seeds/it-can-be-llc.ts

Creates the complete IT CAN BE LLC entity structure:

  1. IT CAN BE LLC (parent holding company)
  2. JEAN ARLENE VENTURING LLC (85% owner, personal income funnel)
  3. ARIBIA LLC (series parent)
  4. ARIBIA LLC - MGMT (management company with two brands)
  5. ARIBIA LLC - CITY STUDIO (property entity)
  6. ARIBIA LLC - APT ARLENE (property entity)
  7. ChittyCorp LLC (pending formation)

Also creates:

  • Property records for City Studio and Apt Arlene
  • User accounts for Nicholas Bianchi and Sharon E Jones
  • Access permissions for each user to appropriate tenants

Run seeding:

npm run db:seed

Note: Only run after pushing the system schema (npm run db:push:system)

Storage Abstraction Layer

Critical Pattern: All database access goes through server/storage.ts. Never write direct Drizzle queries in routes.

⚠️ Important: The current server/storage.ts uses the old shared/schema.ts and needs to be updated to use database/system.schema.ts with tenant-aware queries.

Interface (server/storage.ts:12-42):

export interface IStorage {
  // User operations
  getUser(id: number): Promise<User | undefined>;
  getUserByUsername(username: string): Promise<User | undefined>;
  createUser(user: InsertUser): Promise<User>;

  // Integration operations
  getIntegrations(userId: number): Promise<Integration[]>;
  createIntegration(integration: InsertIntegration): Promise<Integration>;
  updateIntegration(id: number, integration: Partial<Integration>): Promise<Integration | undefined>;

  // Financial summary operations
  getFinancialSummary(userId: number): Promise<FinancialSummary | undefined>;
  createFinancialSummary(summary: InsertFinancialSummary): Promise<FinancialSummary>;

  // Transaction, Task, and AI Message operations...
}

Usage in routes (server/routes.ts):

import { storage } from "./storage";

const user = await storage.getUserByUsername("demo");
const summary = await storage.getFinancialSummary(user.id);

Key Features

1. Hybrid Authentication

Current implementation: Dual-path auth via hybridAuth middleware (server/middleware/auth.ts).

Path 1 — Service token (service-to-service):

  • Authorization: Bearer <CHITTY_AUTH_SERVICE_TOKEN> header
  • Used by ChittyConnect, MCP, and other ChittyOS services

Path 2 — Session cookie (browser clients):

  • cf_session cookie set by POST /api/session (login)
  • KV-backed (FINANCE_KV) with 7-day TTL
  • Session stores { userId }, resolved to full user by callerContext middleware

Session routes (server/routes/session.ts — public, no auth required):

  • GET /api/session — return current user from cookie
  • POST /api/session — login with { email, password }
  • DELETE /api/session — logout (clear cookie + KV)

Password hashing: SHA-256 (Web Crypto in Workers, Node crypto in seed script).

Frontend flow (client/src/App.tsx):

  1. On load, fetches GET /api/session with credentials: "include"
  2. If 401, redirects to /login
  3. Login form POSTs credentials, cookie is set, page reloads

Note: ChittyID integration is planned to replace email/password auth.

2. Financial Dashboard

  • Real-time financial summary: Cash on hand, revenue, expenses, outstanding invoices
  • Data source: Cached in financialSummaries table, fetched from integrations
  • Demo data: Hardcoded values if no summary exists (server/routes.ts:51-55)

3. AI Financial Advice

Location: server/lib/openai.ts

Model: GPT-4o (hardcoded, comment at line 3 warns against changing)

Functions:

  • getFinancialAdvice() - Conversational financial advice based on financial data
  • generateCostReductionPlan() - AI-generated cost reduction strategies

API endpoint: POST /api/ai/message sends user query to OpenAI with financial context

4. Recurring Charge Automation

Location: server/lib/chargeAutomation.ts

Capabilities:

  • Identify recurring charges from integrated services
  • Generate optimization recommendations (cancel, downgrade, consolidate, negotiate)
  • Calculate potential savings

API endpoints:

  • GET /api/recurring-charges - List all recurring charges
  • GET /api/recurring-charges/:id/optimizations - Get optimization suggestions
  • POST /api/recurring-charges/:id/manage - Execute management action

5. Third-Party Integrations (Phase 3 - COMPLETED)

Mercury Bank (server/lib/financialServices.ts:22-51, via ChittyConnect):

  • Real integration through ChittyConnect backend
  • Multi-account support with account selection
  • Static egress IP for bank compliance
  • Fetches balances and transactions
  • Falls back to minimal data in standalone mode
  • Routes: /api/mercury/accounts, /api/mercury/select-accounts
  • Configuration: Requires CHITTYCONNECT_API_BASE + CHITTYCONNECT_API_TOKEN

Wave Accounting (server/lib/wave-api.ts + server/lib/financialServices.ts:54-116):

  • Real integration via OAuth 2.0 + GraphQL API
  • Complete OAuth flow with CSRF protection (HMAC-signed state tokens)
  • Fetches invoices, expenses, and financial summaries
  • Automatic token refresh support
  • Routes: /api/integrations/wave/authorize, /callback, /refresh
  • Requirements: Wave Pro subscription, WAVE_CLIENT_ID, WAVE_CLIENT_SECRET
  • Security: Uses server/lib/oauth-state.ts for secure state token generation/validation

Stripe (server/lib/stripe.ts):

  • Real integration for payment processing
  • Customer management with tenant metadata
  • Checkout session creation (ad-hoc payments)
  • Webhook verification and idempotent event processing
  • Routes: /api/integrations/stripe/connect, /checkout, /webhook
  • Configuration: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET
  • Events stored in webhook_events table (see shared/finance.schema.ts)

DoorLoop (server/lib/financialServices.ts:119-147):

  • Mock data (property management)
  • Returns hardcoded rent roll and maintenance data
  • Real API integration pending

GitHub (server/lib/github.ts):

  • Real GitHub API integration (not mock)
  • Fetches repositories, commits, PRs, issues
  • Used for project cost attribution

Integration Status Monitoring:

  • New endpoint: GET /api/integrations/status (server/routes.ts:122-126)
  • Validates which integrations are properly configured
  • Uses server/lib/integration-validation.ts to check environment variables
  • Returns configuration status for wave, stripe, mercury, openai

Utilities

check_system_operations_duplicates.js

A utility script for analyzing and detecting duplicate operations in system mode. Located at the project root. This script helps maintain code quality when working with ChittyOS integration.

Development Workflows

Adding a New Feature

  1. Update database schema in shared/schema.ts:

    export const newTable = pgTable("new_table", {
      id: serial("id").primaryKey(),
      userId: integer("user_id").notNull().references(() => users.id),
      // ... fields
    });
  2. Run migration: npm run db:push

  3. Add storage methods in server/storage.ts:

    async getNewData(userId: number): Promise<NewData[]> {
      return await db.select().from(newTable).where(eq(newTable.userId, userId));
    }
  4. Create API routes in server/routes.ts:

    api.get("/new-data", async (req: Request, res: Response) => {
      const user = await storage.getUserByUsername("demo");
      const data = await storage.getNewData(user.id);
      res.json(data);
    });
  5. Build frontend in client/src/pages/:

    • Use TanStack Query for data fetching
    • Import shadcn/ui components from @/components/ui/

Working with AI Features

OpenAI Configuration (server/lib/openai.ts):

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const openai = OPENAI_API_KEY ? new OpenAI({ apiKey: OPENAI_API_KEY }) : null;

Best practices:

  • Model is GPT-4o (do not change without user request per comment)
  • Max tokens: 500 for financial advice
  • Include financial context in system prompt
  • Handle API errors gracefully (rate limits, invalid keys)
  • When OPENAI_API_KEY is not set, AI functions return rule-based fallback advice
  • OpenAI client is null when unconfigured — functions guard with if (!openai) early returns

Path Aliases

Configured in tsconfig.json:18-21:

{
  "@/*": ["./client/src/*"],
  "@shared/*": ["./shared/*"]
}

Additional alias in vite.config.ts:25:

"@assets": path.resolve(import.meta.dirname, "attached_assets")

Usage:

import { Button } from "@/components/ui/button";
import { users } from "@shared/schema";
import logo from "@assets/logo.png";

API Endpoints

Authentication (public — no auth required)

  • GET /api/session - Get current user from session cookie
  • POST /api/session - Login with email + password (sets cf_session cookie)
  • DELETE /api/session - Logout (clears cookie + KV entry)

Financial Data

  • GET /api/financial-summary - Get cached financial summary
  • GET /api/transactions - List transactions with optional filters

Integrations

  • GET /api/integrations - List configured integrations
  • GET /api/integrations/status - Check which integrations are properly configured
  • POST /api/integrations - Add new integration
  • PATCH /api/integrations/:id - Update integration credentials
  • GET /api/integrations/events - List webhook events with optional source filter
  • POST /api/admin/events/replay - Replay webhook events to ChittyOS services

Wave Accounting

  • GET /api/integrations/wave/authorize - Start OAuth flow (returns authorization URL)
  • GET /api/integrations/wave/callback - OAuth callback handler (redirects to /connections)
  • POST /api/integrations/wave/refresh - Refresh expired access token

Stripe

  • POST /api/integrations/stripe/connect - Create/fetch Stripe customer
  • POST /api/integrations/stripe/checkout - Create ad-hoc payment session
  • POST /api/integrations/stripe/webhook - Stripe webhook endpoint (signature verified)

Mercury Bank

  • GET /api/mercury/accounts - List available Mercury accounts via ChittyConnect
  • POST /api/mercury/select-accounts - Select which accounts to sync
  • POST /api/integrations/mercury/webhook - Mercury webhook endpoint (authenticated)

Recurring Charges

  • GET /api/charges/recurring - List recurring charges from integrations
  • GET /api/charges/optimizations - Get optimization recommendations
  • POST /api/charges/manage - Cancel/modify a recurring charge

AI Services

  • POST /api/ai/advice - Get initial AI financial advice
  • POST /api/ai/cost-reduction - Generate cost reduction plan
  • POST /api/ai/message - Conversational AI advice (includes previous context)

GitHub Integration

  • GET /api/github/repositories - List user repositories
  • GET /api/github/repositories/:owner/:repo/commits - Get repository commits
  • GET /api/github/repositories/:owner/:repo/pulls - Get pull requests
  • GET /api/github/repositories/:owner/:repo/issues - Get issues

Forensic Accounting

  • GET /api/forensics/investigations - List investigations
  • GET /api/forensics/investigations/:id - Get investigation
  • POST /api/forensics/investigations - Create investigation
  • PATCH /api/forensics/investigations/:id/status - Update status
  • POST /api/forensics/investigations/:id/evidence - Add evidence
  • GET /api/forensics/investigations/:id/evidence - List evidence
  • POST /api/forensics/evidence/:id/custody - Update chain of custody
  • POST /api/forensics/investigations/:id/analyze - Comprehensive analysis
  • POST /api/forensics/investigations/:id/analyze/duplicates - Duplicate payment detection
  • POST /api/forensics/investigations/:id/analyze/timing - Unusual timing detection
  • POST /api/forensics/investigations/:id/analyze/round-dollars - Round dollar anomalies
  • POST /api/forensics/investigations/:id/analyze/benfords-law - Benford's Law analysis
  • POST /api/forensics/investigations/:id/trace-funds - Trace flow of funds
  • POST /api/forensics/investigations/:id/flow-of-funds - Create flow of funds record
  • GET /api/forensics/investigations/:id/flow-of-funds - Get flow of funds
  • POST /api/forensics/investigations/:id/calculate-damages/direct-loss - Direct loss calculation
  • POST /api/forensics/investigations/:id/calculate-damages/net-worth - Net worth method
  • POST /api/forensics/calculate-interest - Pre-judgment interest calculation
  • POST /api/forensics/investigations/:id/generate-summary - Executive summary
  • POST /api/forensics/investigations/:id/reports - Create forensic report
  • GET /api/forensics/investigations/:id/reports - Get forensic reports

Tasks

  • GET /api/tasks - List financial tasks
  • POST /api/tasks - Create task
  • PATCH /api/tasks/:id - Update task
  • DELETE /api/tasks/:id - Delete task

Property CRUD (Phase 4)

  • POST /api/properties - Create property
  • PATCH /api/properties/:id - Update property
  • POST /api/properties/:id/units - Create unit
  • PATCH /api/properties/:id/units/:unitId - Update unit
  • POST /api/properties/:id/leases - Create lease
  • PATCH /api/properties/:id/leases/:leaseId - Update lease

Property Financial Data (Phase 4)

  • GET /api/properties/:id/financials - NOI, cap rate, cash-on-cash, occupancy
  • GET /api/properties/:id/rent-roll - Unit-level rent roll with payment status
  • GET /api/properties/:id/pnl?start=YYYY-MM-DD&end=YYYY-MM-DD - Property P&L by REI category

Property Valuation (Phase 4)

  • GET /api/properties/:id/valuation - Aggregated multi-source valuation estimates
  • POST /api/properties/:id/valuation/refresh - Fetch fresh estimates from all configured providers
  • GET /api/properties/:id/valuation/history - Historical valuation timeline

Data Import (Phase 4)

  • POST /api/import/turbotenant - Import TurboTenant CSV ledger (requires X-Account-ID header)
  • POST /api/import/wave-sync - Sync Wave transactions via OAuth

Environment Configuration

Required Variables

Database (required):

DATABASE_URL="postgresql://user:pass@host/dbname"

Application:

NODE_ENV="development"                              # or "production"
MODE="standalone"                                   # or "system" (multi-tenant)
PUBLIC_APP_BASE_URL="http://localhost:5000"        # Base URL for OAuth redirects

OAuth Security (required for production):

OAUTH_STATE_SECRET="random-secret-32chars"         # HMAC secret for OAuth state tokens

AI & OpenAI (optional for development, required for AI features):

OPENAI_API_KEY="sk-..."                            # Required for AI financial advice

Wave Accounting (Phase 3 - Real Integration):

WAVE_CLIENT_ID="..."                               # OAuth client ID from Wave Developer Portal
WAVE_CLIENT_SECRET="..."                           # OAuth client secret
WAVE_REDIRECT_URI="http://localhost:5000/api/integrations/wave/callback"  # Optional, defaults to PUBLIC_APP_BASE_URL/api/integrations/wave/callback

Stripe (Phase 3 - Real Integration):

STRIPE_SECRET_KEY="sk_test_..."                    # Stripe secret key (test or live)
STRIPE_PUBLISHABLE_KEY="pk_test_..."               # Stripe publishable key (optional, frontend)
STRIPE_WEBHOOK_SECRET="whsec_..."                  # Webhook signing secret for verification

Mercury Bank (Phase 3 - Real Integration via ChittyConnect):

CHITTYCONNECT_API_BASE="https://connect.chitty.cc"  # ChittyConnect backend URL
CHITTYCONNECT_API_TOKEN="..."                       # Service authentication token
CHITTY_CONNECT_URL="https://connect.chitty.cc"      # Frontend redirect URL (optional)

GitHub (optional):

GITHUB_TOKEN="ghp_..."                             # Required for GitHub integration

Property Valuation (Phase 4 - optional, each enables its provider):

ZILLOW_API_KEY="..."                               # RapidAPI key for Zillow estimates
REDFIN_API_KEY="..."                               # RapidAPI key for Redfin estimates
HOUSECANARY_API_KEY="..."                          # HouseCanary API key
ATTOM_API_KEY="..."                                # ATTOM Data Gateway key

Cook County Assessor (Socrata) is always available — no API key required.

Local Development Setup

  1. Provision Neon database:

  2. Initialize schema:

    npm run db:push
  3. Create demo user (manual step required):

    INSERT INTO users (username, password, display_name, email, role)
    VALUES ('demo', 'hashed_password', 'Demo User', 'demo@example.com', 'user');
  4. Start dev server:

    npm run dev
  5. Access application: http://localhost:5000

Testing

Manual Testing

  1. Start dev server: npm run dev
  2. Navigate to http://localhost:5000
  3. Application auto-logs in as "demo" user
  4. Test dashboard, integrations, AI chat
  5. Check browser console for errors
  6. Monitor server logs in terminal

Testing AI Features

Testing Integrations

  • Mercury Bank: Real integration via ChittyConnect (requires CHITTYCONNECT_API_BASE + token)
  • Wave Accounting: Real integration via OAuth 2.0 (requires WAVE_CLIENT_ID + WAVE_CLIENT_SECRET)
  • Stripe: Real integration (requires STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET)
  • DoorLoop, QuickBooks, Xero, Brex, Gusto: Not yet implemented — return empty data with console.warn
  • Unimplemented integration functions return {} or [], not fabricated data

Common Issues & Solutions

Database Connection Errors

Error: DATABASE_URL must be set

Solutions:

  1. Verify DATABASE_URL environment variable is set
  2. Test connection: psql $DATABASE_URL -c "SELECT 1"
  3. Check Neon dashboard for database status
  4. Ensure WebSocket support (ws package installed)

Port 5000 Already in Use

Error: EADDRINUSE: address already in use :::5000

Solution:

lsof -ti:5000 | xargs kill -9

Note: Port cannot be changed (hardcoded for Replit deployment).

OpenAI API Errors

Error: 401 Unauthorized or 429 Rate Limit

Solutions:

  1. Verify OPENAI_API_KEY is valid
  2. Check API key has credits at https://platform.openai.com/account/billing
  3. Implement rate limiting or caching for AI requests
  4. Handle errors gracefully (see server/lib/openai.ts:58-60)

Demo User Not Found

Error: Demo user not found from /api/session

Solution: Create demo user in database:

INSERT INTO users (username, password, display_name, email, role)
VALUES ('demo', 'any_value', 'Demo User', 'demo@example.com', 'user');

Type Checking Failures

Error: TypeScript errors from npm run check

Common causes:

  1. Schema changes not reflected in types (types are auto-generated from schema)
  2. Missing imports from @shared/schema
  3. Path alias not resolving (check tsconfig.json)

Solution: Verify schema exports match usage, run npm run check to see all errors.

ChittyOS Integration Points

ChittyID Integration (Planned)

  • Replace demo authentication with ChittyID
  • Link financial data to ChittyID for cross-platform identity
  • Each user should have associated ChittyID DID

ChittyConnect Integration (Planned)

  • Expose financial summary as MCP resource
  • Provide AI financial advice as MCP tool
  • Enable cross-service financial queries

ChittyChronicle Integration (Planned)

  • Log all financial transactions to audit trail
  • Track AI advice and outcomes
  • Compliance and forensic analysis

Development Best Practices

Database Changes

  1. Update shared/schema.ts (single source of truth)
  2. Run npm run db:push to apply changes
  3. Test with demo user in development
  4. Types are auto-generated from schema (no manual type updates needed)

API Design

  • Always use storage abstraction layer (never direct Drizzle queries in routes)
  • Validate inputs with Zod schemas from @shared/schema
  • Use consistent error handling pattern
  • Return JSON responses with appropriate status codes

Frontend Development

  • Use shadcn/ui components for consistency (@/components/ui/*)
  • Implement responsive design with Tailwind utilities
  • Use TanStack Query for all API calls (handles caching, loading, errors)
  • Optimize re-renders with proper React patterns (memo, useCallback)

Security Considerations

OAuth Security (Phase 3 implemented):

  • CSRF Protection: OAuth state tokens use HMAC-SHA256 signatures (server/lib/oauth-state-edge.ts)
  • Replay Prevention: State tokens expire after 10 minutes (timestamp validation)
  • Tampering Detection: State includes cryptographic signature verified server-side
  • Production Requirement: Set OAUTH_STATE_SECRET to random 32+ character string

Webhook Security:

  • Stripe: Webhook signatures verified using STRIPE_WEBHOOK_SECRET
  • Mercury: Service authentication via serviceAuth middleware
  • Idempotency: All webhook events deduplicated using KV with 7-day TTL

Integration Validation (server/lib/integration-validation.ts):

  • Validates required environment variables before allowing integration connections
  • Returns 503 Service Unavailable if integration not properly configured
  • Prevents cryptic errors from misconfigured services

General Security:

  • Critical: Replace demo authentication before production (ChittyID integration pending)
  • Never commit API keys (use environment variables)
  • Sanitize financial data in logs (mask account numbers)
  • Validate all user inputs on backend (Zod schemas)
  • Use HTTPS in production (HTTP allowed for local dev only)
  • Credential data stored as JSONB in database (encrypted at rest by Neon)

Known Limitations

  1. Session Auth (not ChittyID): Email/password with KV-backed cookies — ChittyID SSO integration pending
  2. DoorLoop Sunset: DoorLoop integration is mock-only — platform is sunset for us, mock code is cleanup candidate
  3. No Migrations: Uses drizzle-kit push (destructive) instead of proper migrations
  4. Forensic Tables Not in System Schema: Forensic tables use integer IDs from shared/schema.ts and may not exist in the production database yet
  5. Legacy Express Code: server/routes.ts, server/storage.ts, server/db.ts are legacy Express code kept for standalone dev reference

Future Enhancements

Phase 1: Complete Multi-Tenant Implementation (COMPLETED)

  • ✅ Database schemas created (system.schema.ts, standalone.schema.ts)
  • ✅ Seeding script for IT CAN BE LLC entities
  • ✅ Mode-aware database connection
  • ✅ Wrangler configuration template with KV/R2 provisioned
  • ✅ SystemStorage with tenant-aware Drizzle queries (server/storage/system.ts)
  • ✅ Service token auth middleware (server/middleware/auth.ts)
  • ✅ Tenant-scoped middleware (server/middleware/tenant.ts)
  • ✅ Hierarchy-aware tenant switcher with consolidated portfolio view (PR feat: hierarchy-aware tenant switcher with consolidated portfolio view #52)

Phase 1.5: Hono Route Migration (COMPLETED)

  • ✅ All 17 route modules migrated from Express to Hono
  • ✅ Edge-compatible: Web Crypto API, Neon HTTP driver, no Node.js dependencies
  • ✅ Per-prefix middleware registration (avoids blocking public routes)
  • ✅ Deployed to Cloudflare Workers at finance.chitty.cc
  • ✅ 30/30 tests passing

Phase 2: ChittyConnect Integration (Partially Completed)

  • ✅ Mercury Bank via ChittyConnect backend (multi-account support)
  • ✅ Registered with ChittyRegistry (did:chitty:REG-XE6835, PR feat: ChittyRegister CI/CD registration pipeline #49)
  • ⏳ Integrate with ChittyConnect MCP
  • ⏳ Log to ChittyChronicle
  • ⏳ Use ChittyAuth tokens

Phase 3: Real Third-Party Integrations (COMPLETED ✅)

  • Wave Accounting - OAuth 2.0 flow + GraphQL API (server/lib/wave-api.ts)
  • Stripe - Payment processing, checkout, webhooks (server/lib/stripe.ts)
  • Mercury Bank - Multi-account via ChittyConnect (static egress IP)
  • OAuth Security - CSRF-protected state tokens (server/lib/oauth-state.ts)
  • Integration Monitoring - Config validation endpoint (/api/integrations/status)
  • Webhook Infrastructure - Idempotent event processing (webhook_events table)
  • DoorLoop - Sunset (mock code is cleanup candidate, not roadmap)

Phase 4: Property Financial API (Partially Completed)

  • property_valuations table in system schema
  • ✅ Property/unit/lease CRUD in SystemStorage
  • ✅ Financial aggregation methods (NOI, cap rate, cash-on-cash, occupancy, rent roll, P&L)
  • ✅ Property mutation + financial endpoints (10 new routes in properties.ts)
  • ✅ Multi-source valuation providers (Zillow, Redfin, HouseCanary, ATTOM, County)
  • ✅ Confidence-weighted valuation aggregation
  • ✅ Valuation routes (current, refresh, history)
  • ✅ TurboTenant CSV import with deduplication
  • ✅ Wave sync import endpoint
  • ✅ Valuation Tab component (client/src/components/property/ValuationTab.tsx) — generalized, takes propertyId prop
  • ✅ Deployed to Cloudflare Workers (35 tests passing)
  • ⏳ Lease expiration notifications
  • ⏳ Frontend property management UI

Phase 5: ChittyOS Ecosystem Integration

  • ⏳ Replace demo auth with ChittyID
  • ⏳ Expose financial data as MCP resources
  • ⏳ Log to ChittyChronicle (audit trail)
  • ⏳ Issue ChittyCert certificates for secure connections

Phase 6: Advanced Features

  • ✅ Consolidated reporting across all entities (server/lib/consolidated-reporting.ts)
  • ✅ Multi-currency schema support (ISO 4217, PR feat: multi-currency schema support (ISO 4217) #53)
  • Inter-company allocation automation
  • Tax optimization and reporting
  • Advanced AI forecasting (beyond GPT-4o)
  • Mobile app (React Native)
  • Export/import (CSV, QFX, OFX)

Phase 7: furnished-condos.com (Planned)

  • Cooperative marketplace for small furnished rental operators (under 10 units)
  • Own distribution (not dependent on Airbnb/VRBO APIs)
  • DRL (Distributed Reputation Ledger) for focal trust scoring
  • Channel manager integration via Hospitable or similar for optional cross-posting
  • Guest-facing search + booking frontend
  • Operator onboarding + SaaS billing
  • Local data partnerships with trust-scored vendors

Related Documentation

@github-actions
Copy link
Copy Markdown
Contributor

@coderabbitai review

Please evaluate:

  • Security implications
  • Credential exposure risk
  • Dependency supply chain concerns
  • Breaking API changes

@claude
Copy link
Copy Markdown

claude bot commented Mar 25, 2026

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Canonical URI: chittycanon://core/services/chittyfinance | Tier 3 (Service Layer) | finance.chitty.cc

Project Overview

ChittyFinance is a full-stack financial management platform for the ChittyOS ecosystem. It provides intelligent financial tracking, AI-powered advice, recurring charge optimization, and integrations with Mercury Bank, Wave Accounting, and Stripe payments.

Architecture: Hono on Cloudflare Workers (production) + legacy Express (local dev). PostgreSQL (Neon) with Drizzle ORM. React frontend.

Essential Commands

Development

npm install              # Install dependencies
npm run dev              # Auto-detect mode and start dev server on port 5000
npm run dev:standalone   # Start in standalone mode (local development)
npm run dev:system       # Start in system mode (ChittyOS integration)
npm run check            # TypeScript type checking
npm run mode:detect      # Detect and display current mode
npm run mode:setup       # Setup mode configuration (script not yet implemented)

Build & Deployment

npm run build            # Build system mode (default for production)
npm run build:standalone # Build standalone mode (outputs to dist/standalone)
npm run build:system     # Build system mode (outputs to dist/system)
npm run build:both       # Build both modes
npm run start            # Run standalone production build locally
npm run deploy           # Deploy to Cloudflare Workers (system mode)
npm run deploy:staging   # Deploy to staging environment
npm run deploy:production # Deploy to production environment

Database Operations

npm run db:push              # Push schema changes (uses current drizzle.config.ts)
npm run db:push:system       # Push system schema to PostgreSQL
npm run db:push:standalone   # Push standalone schema to SQLite
npm run db:seed              # Seed IT CAN BE LLC entity structure

First-Time Setup (System Mode):

MODE=system npm run db:push:system   # Create tables
npm run db:seed                      # Create tenants and users

First-Time Setup (Standalone Mode):

npm run db:push:standalone   # Create SQLite tables
# No seeding needed - single user mode

Critical:

  • Port 5001 is used in server/index.ts for local Express development
  • Server uses reusePort: true for multiple process support on the same port

Architecture

Dual-Mode Operation

ChittyFinance supports two operational modes (controlled by MODE environment variable):

Standalone Mode (default for local development):

  • SQLite database for quick local development
  • Single-tenant (no multi-tenancy overhead)
  • Simplified schema in database/standalone.schema.ts
  • Build output: dist/standalone/
  • Database file: ./chittyfinance.db
  • Run: npm run dev or npm run dev:standalone

System Mode (production - multi-tenant):

  • PostgreSQL (Neon) with full multi-tenancy
  • Supports IT CAN BE LLC entity structure
  • Complete schema in database/system.schema.ts
  • Build output: dist/system/
  • Cloudflare Workers deployment
  • Run: npm run dev:system
  • Deploy: npm run deploy or npm run deploy:production

Mode Detection:

  • Default: npm run dev runs in standalone mode
  • Explicitly set: MODE=system npm run dev
  • Auto-detection script: npm run mode:detect

Multi-Tenant Architecture (System Mode)

IT CAN BE LLC Entity Structure:

IT CAN BE LLC (holding)
├── JEAN ARLENE VENTURING LLC (personal, 85% owner)
├── ARIBIA LLC (series, 100% owned)
│   ├── ARIBIA LLC - MGMT (management)
│   │   ├── Chicago Furnished Condos (consumer brand)
│   │   └── Chitty Services (vendor/tech services)
│   ├── ARIBIA LLC - CITY STUDIO (property)
│   │   └── 550 W Surf St C211, Chicago IL
│   └── ARIBIA LLC - APT ARLENE (property)
│       └── 4343 N Clarendon #1610, Chicago IL
└── ChittyCorp LLC (holding, pending formation)

Tenant Types:

  • holding - Holding companies (IT CAN BE LLC, ChittyCorp LLC)
  • series - Series LLCs (ARIBIA LLC)
  • property - Property holding entities (City Studio, Apt Arlene)
  • management - Management companies (ARIBIA LLC - MGMT)
  • personal - Personal entities (JEAN ARLENE VENTURING LLC)

Key Features:

  • Each tenant has isolated financial data
  • Inter-company transaction tracking
  • Property-specific rent roll and lease management
  • User access control per tenant (roles: owner, admin, manager, viewer)
  • Consolidated reporting across entities

Tech Stack

  • Frontend: React 18 with TypeScript, Wouter (routing), shadcn/ui (Radix UI components)
  • Backend: Hono (Cloudflare Workers, production) / Express.js (legacy, local dev)
  • Database: Neon PostgreSQL with Drizzle ORM
  • Build: Vite (frontend), esbuild (backend)
  • Styling: Tailwind CSS with tailwindcss-animate
  • State: TanStack React Query for server state
  • Payments: Stripe integration
  • AI: OpenAI GPT-4o for financial advice

Project Structure

chittyfinance/
├── client/                 # React frontend (Vite root)
│   └── src/
│       ├── pages/         # Page components (Dashboard, Settings, Landing)
│       ├── components/    # Reusable UI components (shadcn/ui)
│       ├── hooks/         # Custom React hooks
│       └── lib/           # Client utilities
├── server/                # Hono backend (Cloudflare Workers)
│   ├── app.ts            # Hono app factory with middleware wiring
│   ├── env.ts            # HonoEnv type (Bindings + Variables)
│   ├── worker.ts         # Cloudflare Workers entry point
│   ├── index.ts          # Legacy Express entry (standalone dev)
│   ├── routes.ts         # Legacy Express routes (reference only)
│   ├── routes/            # Hono route modules (20 files)
│   │   ├── health.ts     # /health, /api/v1/status
│   │   ├── docs.ts       # /api/v1/documentation (OpenAPI spec)
│   │   ├── accounts.ts   # /api/accounts
│   │   ├── summary.ts    # /api/summary
│   │   ├── tenants.ts    # /api/tenants
│   │   ├── properties.ts # /api/properties (CRUD + financials + rent roll + P&L)
│   │   ├── transactions.ts # /api/transactions
│   │   ├── integrations.ts # /api/integrations
│   │   ├── tasks.ts      # /api/tasks
│   │   ├── ai.ts         # /api/ai-messages
│   │   ├── mercury.ts    # /api/mercury (via ChittyConnect)
│   │   ├── github.ts     # /api/github
│   │   ├── stripe.ts     # /api/integrations/stripe
│   │   ├── wave.ts       # /api/integrations/wave (OAuth)
│   │   ├── charges.ts    # /api/charges (recurring)
│   │   ├── forensics.ts  # /api/forensics (21 endpoints)
│   │   ├── valuation.ts  # /api/properties/:id/valuation (multi-source AVM)
│   │   ├── session.ts    # /api/session (KV-backed cookie auth)
│   │   ├── import.ts     # /api/import (TurboTenant CSV + Wave sync)
│   │   └── webhooks.ts   # Stripe/Mercury webhooks
│   ├── middleware/        # auth, tenant, error middleware
│   ├── storage/           # SystemStorage (Drizzle queries)
│   ├── db/                # connection.ts (Neon HTTP), schema.ts
│   └── lib/               # Server utilities
│       ├── wave-api.ts           # Wave Accounting GraphQL client
│       ├── oauth-state-edge.ts   # Edge-compatible HMAC OAuth state
│       ├── chargeAutomation.ts   # Recurring charge analysis (stubs)
│       ├── forensicService.ts    # Forensic algorithms (legacy)
│       └── valuation/            # Property valuation providers
│           ├── types.ts          # ValuationProvider interface, AggregatedValuation
│           ├── zillow.ts         # Zillow via RapidAPI
│           ├── redfin.ts         # Redfin via RapidAPI
│           ├── housecanary.ts    # HouseCanary REST API
│           ├── attom.ts          # ATTOM Data Gateway
│           ├── county.ts         # Cook County Assessor (Socrata)
│           └── index.ts          # Provider registry + confidence-weighted aggregation
├── database/              # Schema definitions
│   ├── system.schema.ts   # Multi-tenant PostgreSQL (UUID-based)
│   └── standalone.schema.ts # Single-tenant SQLite
├── shared/                # Shared types and schemas
│   └── schema.ts         # Legacy schema with forensic tables (integer-ID)
└── deploy/
    └── system-wrangler.toml # Cloudflare Workers config

Database Architecture

System Mode Schema (Multi-Tenant PostgreSQL)

Location: database/system.schema.ts

Core Tables:

  • tenants - Legal entities (LLCs, properties, management companies)
  • users - User accounts with email/password + optional ChittyID
  • tenant_users - User access to tenants with role-based permissions
  • accounts - Bank accounts, credit cards (tenant-scoped)
  • transactions - Financial transactions with decimal precision
  • intercompany_transactions - Transfers between tenants

Property Management Tables:

  • properties - Real estate assets
  • units - Rental units (if property has multiple units)
  • leases - Tenant leases with rent and dates
  • property_valuations - Cached AVM estimates from external providers (Zillow, Redfin, HouseCanary, ATTOM, County)

Supporting Tables:

  • integrations - Mercury/Wave/Stripe API connections
  • tasks - Financial tasks
  • ai_messages - AI conversation history

Key Characteristics:

  • UUIDs for primary keys (better for distributed systems)
  • Decimal precision for all monetary amounts (12,2)
  • Full multi-tenancy with tenant isolation
  • Hierarchical tenants (parent-child relationships)
  • Indexed for performance (tenant_id, date, etc.)

Standalone Mode Schema (Single-Tenant SQLite)

Location: database/standalone.schema.ts

Simplified Tables:

  • users, accounts, transactions, properties, tasks, integrations

Key Characteristics:

  • Text IDs (simpler for SQLite)
  • Real (float) for amounts (acceptable for dev)
  • No multi-tenancy (single user)
  • Faster for local development

Database Connection

Mode-Aware Connection (server/db.ts):

The database connection automatically switches based on MODE environment variable:

System Mode (MODE=system):

// PostgreSQL (Neon) with multi-tenant schema
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzleNeon({ client: pool, schema: systemSchema });

Standalone Mode (MODE=standalone):

// SQLite for local development
const sqlite = new Database('./chittyfinance.db');
const db = drizzleBetterSqlite(sqlite, { schema: standaloneSchema });

Environment Variables:

  • System mode: DATABASE_URL (Neon PostgreSQL connection string)
  • Standalone mode: SQLITE_FILE (optional, defaults to ./chittyfinance.db)

Database Seeding (System Mode)

Seed Script: database/seeds/it-can-be-llc.ts

Creates the complete IT CAN BE LLC entity structure:

  1. IT CAN BE LLC (parent holding company)
  2. JEAN ARLENE VENTURING LLC (85% owner, personal income funnel)
  3. ARIBIA LLC (series parent)
  4. ARIBIA LLC - MGMT (management company with two brands)
  5. ARIBIA LLC - CITY STUDIO (property entity)
  6. ARIBIA LLC - APT ARLENE (property entity)
  7. ChittyCorp LLC (pending formation)

Also creates:

  • Property records for City Studio and Apt Arlene
  • User accounts for Nicholas Bianchi and Sharon E Jones
  • Access permissions for each user to appropriate tenants

Run seeding:

npm run db:seed

Note: Only run after pushing the system schema (npm run db:push:system)

Storage Abstraction Layer

Critical Pattern: All database access goes through server/storage.ts. Never write direct Drizzle queries in routes.

⚠️ Important: The current server/storage.ts uses the old shared/schema.ts and needs to be updated to use database/system.schema.ts with tenant-aware queries.

Interface (server/storage.ts:12-42):

export interface IStorage {
  // User operations
  getUser(id: number): Promise<User | undefined>;
  getUserByUsername(username: string): Promise<User | undefined>;
  createUser(user: InsertUser): Promise<User>;

  // Integration operations
  getIntegrations(userId: number): Promise<Integration[]>;
  createIntegration(integration: InsertIntegration): Promise<Integration>;
  updateIntegration(id: number, integration: Partial<Integration>): Promise<Integration | undefined>;

  // Financial summary operations
  getFinancialSummary(userId: number): Promise<FinancialSummary | undefined>;
  createFinancialSummary(summary: InsertFinancialSummary): Promise<FinancialSummary>;

  // Transaction, Task, and AI Message operations...
}

Usage in routes (server/routes.ts):

import { storage } from "./storage";

const user = await storage.getUserByUsername("demo");
const summary = await storage.getFinancialSummary(user.id);

Key Features

1. Hybrid Authentication

Current implementation: Dual-path auth via hybridAuth middleware (server/middleware/auth.ts).

Path 1 — Service token (service-to-service):

  • Authorization: Bearer <CHITTY_AUTH_SERVICE_TOKEN> header
  • Used by ChittyConnect, MCP, and other ChittyOS services

Path 2 — Session cookie (browser clients):

  • cf_session cookie set by POST /api/session (login)
  • KV-backed (FINANCE_KV) with 7-day TTL
  • Session stores { userId }, resolved to full user by callerContext middleware

Session routes (server/routes/session.ts — public, no auth required):

  • GET /api/session — return current user from cookie
  • POST /api/session — login with { email, password }
  • DELETE /api/session — logout (clear cookie + KV)

Password hashing: SHA-256 (Web Crypto in Workers, Node crypto in seed script).

Frontend flow (client/src/App.tsx):

  1. On load, fetches GET /api/session with credentials: "include"
  2. If 401, redirects to /login
  3. Login form POSTs credentials, cookie is set, page reloads

Note: ChittyID integration is planned to replace email/password auth.

2. Financial Dashboard

  • Real-time financial summary: Cash on hand, revenue, expenses, outstanding invoices
  • Data source: Cached in financialSummaries table, fetched from integrations
  • Demo data: Hardcoded values if no summary exists (server/routes.ts:51-55)

3. AI Financial Advice

Location: server/lib/openai.ts

Model: GPT-4o (hardcoded, comment at line 3 warns against changing)

Functions:

  • getFinancialAdvice() - Conversational financial advice based on financial data
  • generateCostReductionPlan() - AI-generated cost reduction strategies

API endpoint: POST /api/ai/message sends user query to OpenAI with financial context

4. Recurring Charge Automation

Location: server/lib/chargeAutomation.ts

Capabilities:

  • Identify recurring charges from integrated services
  • Generate optimization recommendations (cancel, downgrade, consolidate, negotiate)
  • Calculate potential savings

API endpoints:

  • GET /api/recurring-charges - List all recurring charges
  • GET /api/recurring-charges/:id/optimizations - Get optimization suggestions
  • POST /api/recurring-charges/:id/manage - Execute management action

5. Third-Party Integrations (Phase 3 - COMPLETED)

Mercury Bank (server/lib/financialServices.ts:22-51, via ChittyConnect):

  • Real integration through ChittyConnect backend
  • Multi-account support with account selection
  • Static egress IP for bank compliance
  • Fetches balances and transactions
  • Falls back to minimal data in standalone mode
  • Routes: /api/mercury/accounts, /api/mercury/select-accounts
  • Configuration: Requires CHITTYCONNECT_API_BASE + CHITTYCONNECT_API_TOKEN

Wave Accounting (server/lib/wave-api.ts + server/lib/financialServices.ts:54-116):

  • Real integration via OAuth 2.0 + GraphQL API
  • Complete OAuth flow with CSRF protection (HMAC-signed state tokens)
  • Fetches invoices, expenses, and financial summaries
  • Automatic token refresh support
  • Routes: /api/integrations/wave/authorize, /callback, /refresh
  • Requirements: Wave Pro subscription, WAVE_CLIENT_ID, WAVE_CLIENT_SECRET
  • Security: Uses server/lib/oauth-state.ts for secure state token generation/validation

Stripe (server/lib/stripe.ts):

  • Real integration for payment processing
  • Customer management with tenant metadata
  • Checkout session creation (ad-hoc payments)
  • Webhook verification and idempotent event processing
  • Routes: /api/integrations/stripe/connect, /checkout, /webhook
  • Configuration: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET
  • Events stored in webhook_events table (see shared/finance.schema.ts)

DoorLoop (server/lib/financialServices.ts:119-147):

  • Mock data (property management)
  • Returns hardcoded rent roll and maintenance data
  • Real API integration pending

GitHub (server/lib/github.ts):

  • Real GitHub API integration (not mock)
  • Fetches repositories, commits, PRs, issues
  • Used for project cost attribution

Integration Status Monitoring:

  • New endpoint: GET /api/integrations/status (server/routes.ts:122-126)
  • Validates which integrations are properly configured
  • Uses server/lib/integration-validation.ts to check environment variables
  • Returns configuration status for wave, stripe, mercury, openai

Utilities

check_system_operations_duplicates.js

A utility script for analyzing and detecting duplicate operations in system mode. Located at the project root. This script helps maintain code quality when working with ChittyOS integration.

Development Workflows

Adding a New Feature

  1. Update database schema in shared/schema.ts:

    export const newTable = pgTable("new_table", {
      id: serial("id").primaryKey(),
      userId: integer("user_id").notNull().references(() => users.id),
      // ... fields
    });
  2. Run migration: npm run db:push

  3. Add storage methods in server/storage.ts:

    async getNewData(userId: number): Promise<NewData[]> {
      return await db.select().from(newTable).where(eq(newTable.userId, userId));
    }
  4. Create API routes in server/routes.ts:

    api.get("/new-data", async (req: Request, res: Response) => {
      const user = await storage.getUserByUsername("demo");
      const data = await storage.getNewData(user.id);
      res.json(data);
    });
  5. Build frontend in client/src/pages/:

    • Use TanStack Query for data fetching
    • Import shadcn/ui components from @/components/ui/

Working with AI Features

OpenAI Configuration (server/lib/openai.ts):

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const openai = OPENAI_API_KEY ? new OpenAI({ apiKey: OPENAI_API_KEY }) : null;

Best practices:

  • Model is GPT-4o (do not change without user request per comment)
  • Max tokens: 500 for financial advice
  • Include financial context in system prompt
  • Handle API errors gracefully (rate limits, invalid keys)
  • When OPENAI_API_KEY is not set, AI functions return rule-based fallback advice
  • OpenAI client is null when unconfigured — functions guard with if (!openai) early returns

Path Aliases

Configured in tsconfig.json:18-21:

{
  "@/*": ["./client/src/*"],
  "@shared/*": ["./shared/*"]
}

Additional alias in vite.config.ts:25:

"@assets": path.resolve(import.meta.dirname, "attached_assets")

Usage:

import { Button } from "@/components/ui/button";
import { users } from "@shared/schema";
import logo from "@assets/logo.png";

API Endpoints

Authentication (public — no auth required)

  • GET /api/session - Get current user from session cookie
  • POST /api/session - Login with email + password (sets cf_session cookie)
  • DELETE /api/session - Logout (clears cookie + KV entry)

Financial Data

  • GET /api/financial-summary - Get cached financial summary
  • GET /api/transactions - List transactions with optional filters

Integrations

  • GET /api/integrations - List configured integrations
  • GET /api/integrations/status - Check which integrations are properly configured
  • POST /api/integrations - Add new integration
  • PATCH /api/integrations/:id - Update integration credentials
  • GET /api/integrations/events - List webhook events with optional source filter
  • POST /api/admin/events/replay - Replay webhook events to ChittyOS services

Wave Accounting

  • GET /api/integrations/wave/authorize - Start OAuth flow (returns authorization URL)
  • GET /api/integrations/wave/callback - OAuth callback handler (redirects to /connections)
  • POST /api/integrations/wave/refresh - Refresh expired access token

Stripe

  • POST /api/integrations/stripe/connect - Create/fetch Stripe customer
  • POST /api/integrations/stripe/checkout - Create ad-hoc payment session
  • POST /api/integrations/stripe/webhook - Stripe webhook endpoint (signature verified)

Mercury Bank

  • GET /api/mercury/accounts - List available Mercury accounts via ChittyConnect
  • POST /api/mercury/select-accounts - Select which accounts to sync
  • POST /api/integrations/mercury/webhook - Mercury webhook endpoint (authenticated)

Recurring Charges

  • GET /api/charges/recurring - List recurring charges from integrations
  • GET /api/charges/optimizations - Get optimization recommendations
  • POST /api/charges/manage - Cancel/modify a recurring charge

AI Services

  • POST /api/ai/advice - Get initial AI financial advice
  • POST /api/ai/cost-reduction - Generate cost reduction plan
  • POST /api/ai/message - Conversational AI advice (includes previous context)

GitHub Integration

  • GET /api/github/repositories - List user repositories
  • GET /api/github/repositories/:owner/:repo/commits - Get repository commits
  • GET /api/github/repositories/:owner/:repo/pulls - Get pull requests
  • GET /api/github/repositories/:owner/:repo/issues - Get issues

Forensic Accounting

  • GET /api/forensics/investigations - List investigations
  • GET /api/forensics/investigations/:id - Get investigation
  • POST /api/forensics/investigations - Create investigation
  • PATCH /api/forensics/investigations/:id/status - Update status
  • POST /api/forensics/investigations/:id/evidence - Add evidence
  • GET /api/forensics/investigations/:id/evidence - List evidence
  • POST /api/forensics/evidence/:id/custody - Update chain of custody
  • POST /api/forensics/investigations/:id/analyze - Comprehensive analysis
  • POST /api/forensics/investigations/:id/analyze/duplicates - Duplicate payment detection
  • POST /api/forensics/investigations/:id/analyze/timing - Unusual timing detection
  • POST /api/forensics/investigations/:id/analyze/round-dollars - Round dollar anomalies
  • POST /api/forensics/investigations/:id/analyze/benfords-law - Benford's Law analysis
  • POST /api/forensics/investigations/:id/trace-funds - Trace flow of funds
  • POST /api/forensics/investigations/:id/flow-of-funds - Create flow of funds record
  • GET /api/forensics/investigations/:id/flow-of-funds - Get flow of funds
  • POST /api/forensics/investigations/:id/calculate-damages/direct-loss - Direct loss calculation
  • POST /api/forensics/investigations/:id/calculate-damages/net-worth - Net worth method
  • POST /api/forensics/calculate-interest - Pre-judgment interest calculation
  • POST /api/forensics/investigations/:id/generate-summary - Executive summary
  • POST /api/forensics/investigations/:id/reports - Create forensic report
  • GET /api/forensics/investigations/:id/reports - Get forensic reports

Tasks

  • GET /api/tasks - List financial tasks
  • POST /api/tasks - Create task
  • PATCH /api/tasks/:id - Update task
  • DELETE /api/tasks/:id - Delete task

Property CRUD (Phase 4)

  • POST /api/properties - Create property
  • PATCH /api/properties/:id - Update property
  • POST /api/properties/:id/units - Create unit
  • PATCH /api/properties/:id/units/:unitId - Update unit
  • POST /api/properties/:id/leases - Create lease
  • PATCH /api/properties/:id/leases/:leaseId - Update lease

Property Financial Data (Phase 4)

  • GET /api/properties/:id/financials - NOI, cap rate, cash-on-cash, occupancy
  • GET /api/properties/:id/rent-roll - Unit-level rent roll with payment status
  • GET /api/properties/:id/pnl?start=YYYY-MM-DD&end=YYYY-MM-DD - Property P&L by REI category

Property Valuation (Phase 4)

  • GET /api/properties/:id/valuation - Aggregated multi-source valuation estimates
  • POST /api/properties/:id/valuation/refresh - Fetch fresh estimates from all configured providers
  • GET /api/properties/:id/valuation/history - Historical valuation timeline

Data Import (Phase 4)

  • POST /api/import/turbotenant - Import TurboTenant CSV ledger (requires X-Account-ID header)
  • POST /api/import/wave-sync - Sync Wave transactions via OAuth

Environment Configuration

Required Variables

Database (required):

DATABASE_URL="postgresql://user:pass@host/dbname"

Application:

NODE_ENV="development"                              # or "production"
MODE="standalone"                                   # or "system" (multi-tenant)
PUBLIC_APP_BASE_URL="http://localhost:5000"        # Base URL for OAuth redirects

OAuth Security (required for production):

OAUTH_STATE_SECRET="random-secret-32chars"         # HMAC secret for OAuth state tokens

AI & OpenAI (optional for development, required for AI features):

OPENAI_API_KEY="sk-..."                            # Required for AI financial advice

Wave Accounting (Phase 3 - Real Integration):

WAVE_CLIENT_ID="..."                               # OAuth client ID from Wave Developer Portal
WAVE_CLIENT_SECRET="..."                           # OAuth client secret
WAVE_REDIRECT_URI="http://localhost:5000/api/integrations/wave/callback"  # Optional, defaults to PUBLIC_APP_BASE_URL/api/integrations/wave/callback

Stripe (Phase 3 - Real Integration):

STRIPE_SECRET_KEY="sk_test_..."                    # Stripe secret key (test or live)
STRIPE_PUBLISHABLE_KEY="pk_test_..."               # Stripe publishable key (optional, frontend)
STRIPE_WEBHOOK_SECRET="whsec_..."                  # Webhook signing secret for verification

Mercury Bank (Phase 3 - Real Integration via ChittyConnect):

CHITTYCONNECT_API_BASE="https://connect.chitty.cc"  # ChittyConnect backend URL
CHITTYCONNECT_API_TOKEN="..."                       # Service authentication token
CHITTY_CONNECT_URL="https://connect.chitty.cc"      # Frontend redirect URL (optional)

GitHub (optional):

GITHUB_TOKEN="ghp_..."                             # Required for GitHub integration

Property Valuation (Phase 4 - optional, each enables its provider):

ZILLOW_API_KEY="..."                               # RapidAPI key for Zillow estimates
REDFIN_API_KEY="..."                               # RapidAPI key for Redfin estimates
HOUSECANARY_API_KEY="..."                          # HouseCanary API key
ATTOM_API_KEY="..."                                # ATTOM Data Gateway key

Cook County Assessor (Socrata) is always available — no API key required.

Local Development Setup

  1. Provision Neon database:

  2. Initialize schema:

    npm run db:push
  3. Create demo user (manual step required):

    INSERT INTO users (username, password, display_name, email, role)
    VALUES ('demo', 'hashed_password', 'Demo User', 'demo@example.com', 'user');
  4. Start dev server:

    npm run dev
  5. Access application: http://localhost:5000

Testing

Manual Testing

  1. Start dev server: npm run dev
  2. Navigate to http://localhost:5000
  3. Application auto-logs in as "demo" user
  4. Test dashboard, integrations, AI chat
  5. Check browser console for errors
  6. Monitor server logs in terminal

Testing AI Features

Testing Integrations

  • Mercury Bank: Real integration via ChittyConnect (requires CHITTYCONNECT_API_BASE + token)
  • Wave Accounting: Real integration via OAuth 2.0 (requires WAVE_CLIENT_ID + WAVE_CLIENT_SECRET)
  • Stripe: Real integration (requires STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET)
  • DoorLoop, QuickBooks, Xero, Brex, Gusto: Not yet implemented — return empty data with console.warn
  • Unimplemented integration functions return {} or [], not fabricated data

Common Issues & Solutions

Database Connection Errors

Error: DATABASE_URL must be set

Solutions:

  1. Verify DATABASE_URL environment variable is set
  2. Test connection: psql $DATABASE_URL -c "SELECT 1"
  3. Check Neon dashboard for database status
  4. Ensure WebSocket support (ws package installed)

Port 5000 Already in Use

Error: EADDRINUSE: address already in use :::5000

Solution:

lsof -ti:5000 | xargs kill -9

Note: Port cannot be changed (hardcoded for Replit deployment).

OpenAI API Errors

Error: 401 Unauthorized or 429 Rate Limit

Solutions:

  1. Verify OPENAI_API_KEY is valid
  2. Check API key has credits at https://platform.openai.com/account/billing
  3. Implement rate limiting or caching for AI requests
  4. Handle errors gracefully (see server/lib/openai.ts:58-60)

Demo User Not Found

Error: Demo user not found from /api/session

Solution: Create demo user in database:

INSERT INTO users (username, password, display_name, email, role)
VALUES ('demo', 'any_value', 'Demo User', 'demo@example.com', 'user');

Type Checking Failures

Error: TypeScript errors from npm run check

Common causes:

  1. Schema changes not reflected in types (types are auto-generated from schema)
  2. Missing imports from @shared/schema
  3. Path alias not resolving (check tsconfig.json)

Solution: Verify schema exports match usage, run npm run check to see all errors.

ChittyOS Integration Points

ChittyID Integration (Planned)

  • Replace demo authentication with ChittyID
  • Link financial data to ChittyID for cross-platform identity
  • Each user should have associated ChittyID DID

ChittyConnect Integration (Planned)

  • Expose financial summary as MCP resource
  • Provide AI financial advice as MCP tool
  • Enable cross-service financial queries

ChittyChronicle Integration (Planned)

  • Log all financial transactions to audit trail
  • Track AI advice and outcomes
  • Compliance and forensic analysis

Development Best Practices

Database Changes

  1. Update shared/schema.ts (single source of truth)
  2. Run npm run db:push to apply changes
  3. Test with demo user in development
  4. Types are auto-generated from schema (no manual type updates needed)

API Design

  • Always use storage abstraction layer (never direct Drizzle queries in routes)
  • Validate inputs with Zod schemas from @shared/schema
  • Use consistent error handling pattern
  • Return JSON responses with appropriate status codes

Frontend Development

  • Use shadcn/ui components for consistency (@/components/ui/*)
  • Implement responsive design with Tailwind utilities
  • Use TanStack Query for all API calls (handles caching, loading, errors)
  • Optimize re-renders with proper React patterns (memo, useCallback)

Security Considerations

OAuth Security (Phase 3 implemented):

  • CSRF Protection: OAuth state tokens use HMAC-SHA256 signatures (server/lib/oauth-state-edge.ts)
  • Replay Prevention: State tokens expire after 10 minutes (timestamp validation)
  • Tampering Detection: State includes cryptographic signature verified server-side
  • Production Requirement: Set OAUTH_STATE_SECRET to random 32+ character string

Webhook Security:

  • Stripe: Webhook signatures verified using STRIPE_WEBHOOK_SECRET
  • Mercury: Service authentication via serviceAuth middleware
  • Idempotency: All webhook events deduplicated using KV with 7-day TTL

Integration Validation (server/lib/integration-validation.ts):

  • Validates required environment variables before allowing integration connections
  • Returns 503 Service Unavailable if integration not properly configured
  • Prevents cryptic errors from misconfigured services

General Security:

  • Critical: Replace demo authentication before production (ChittyID integration pending)
  • Never commit API keys (use environment variables)
  • Sanitize financial data in logs (mask account numbers)
  • Validate all user inputs on backend (Zod schemas)
  • Use HTTPS in production (HTTP allowed for local dev only)
  • Credential data stored as JSONB in database (encrypted at rest by Neon)

Known Limitations

  1. Session Auth (not ChittyID): Email/password with KV-backed cookies — ChittyID SSO integration pending
  2. DoorLoop Sunset: DoorLoop integration is mock-only — platform is sunset for us, mock code is cleanup candidate
  3. No Migrations: Uses drizzle-kit push (destructive) instead of proper migrations
  4. Forensic Tables Not in System Schema: Forensic tables use integer IDs from shared/schema.ts and may not exist in the production database yet
  5. Legacy Express Code: server/routes.ts, server/storage.ts, server/db.ts are legacy Express code kept for standalone dev reference

Future Enhancements

Phase 1: Complete Multi-Tenant Implementation (COMPLETED)

  • ✅ Database schemas created (system.schema.ts, standalone.schema.ts)
  • ✅ Seeding script for IT CAN BE LLC entities
  • ✅ Mode-aware database connection
  • ✅ Wrangler configuration template with KV/R2 provisioned
  • ✅ SystemStorage with tenant-aware Drizzle queries (server/storage/system.ts)
  • ✅ Service token auth middleware (server/middleware/auth.ts)
  • ✅ Tenant-scoped middleware (server/middleware/tenant.ts)
  • ✅ Hierarchy-aware tenant switcher with consolidated portfolio view (PR feat: hierarchy-aware tenant switcher with consolidated portfolio view #52)

Phase 1.5: Hono Route Migration (COMPLETED)

  • ✅ All 17 route modules migrated from Express to Hono
  • ✅ Edge-compatible: Web Crypto API, Neon HTTP driver, no Node.js dependencies
  • ✅ Per-prefix middleware registration (avoids blocking public routes)
  • ✅ Deployed to Cloudflare Workers at finance.chitty.cc
  • ✅ 30/30 tests passing

Phase 2: ChittyConnect Integration (Partially Completed)

  • ✅ Mercury Bank via ChittyConnect backend (multi-account support)
  • ✅ Registered with ChittyRegistry (did:chitty:REG-XE6835, PR feat: ChittyRegister CI/CD registration pipeline #49)
  • ⏳ Integrate with ChittyConnect MCP
  • ⏳ Log to ChittyChronicle
  • ⏳ Use ChittyAuth tokens

Phase 3: Real Third-Party Integrations (COMPLETED ✅)

  • Wave Accounting - OAuth 2.0 flow + GraphQL API (server/lib/wave-api.ts)
  • Stripe - Payment processing, checkout, webhooks (server/lib/stripe.ts)
  • Mercury Bank - Multi-account via ChittyConnect (static egress IP)
  • OAuth Security - CSRF-protected state tokens (server/lib/oauth-state.ts)
  • Integration Monitoring - Config validation endpoint (/api/integrations/status)
  • Webhook Infrastructure - Idempotent event processing (webhook_events table)
  • DoorLoop - Sunset (mock code is cleanup candidate, not roadmap)

Phase 4: Property Financial API (Partially Completed)

  • property_valuations table in system schema
  • ✅ Property/unit/lease CRUD in SystemStorage
  • ✅ Financial aggregation methods (NOI, cap rate, cash-on-cash, occupancy, rent roll, P&L)
  • ✅ Property mutation + financial endpoints (10 new routes in properties.ts)
  • ✅ Multi-source valuation providers (Zillow, Redfin, HouseCanary, ATTOM, County)
  • ✅ Confidence-weighted valuation aggregation
  • ✅ Valuation routes (current, refresh, history)
  • ✅ TurboTenant CSV import with deduplication
  • ✅ Wave sync import endpoint
  • ✅ Valuation Tab component (client/src/components/property/ValuationTab.tsx) — generalized, takes propertyId prop
  • ✅ Deployed to Cloudflare Workers (35 tests passing)
  • ⏳ Lease expiration notifications
  • ⏳ Frontend property management UI

Phase 5: ChittyOS Ecosystem Integration

  • ⏳ Replace demo auth with ChittyID
  • ⏳ Expose financial data as MCP resources
  • ⏳ Log to ChittyChronicle (audit trail)
  • ⏳ Issue ChittyCert certificates for secure connections

Phase 6: Advanced Features

  • ✅ Consolidated reporting across all entities (server/lib/consolidated-reporting.ts)
  • ✅ Multi-currency schema support (ISO 4217, PR feat: multi-currency schema support (ISO 4217) #53)
  • Inter-company allocation automation
  • Tax optimization and reporting
  • Advanced AI forecasting (beyond GPT-4o)
  • Mobile app (React Native)
  • Export/import (CSV, QFX, OFX)

Phase 7: furnished-condos.com (Planned)

  • Cooperative marketplace for small furnished rental operators (under 10 units)
  • Own distribution (not dependent on Airbnb/VRBO APIs)
  • DRL (Distributed Reputation Ledger) for focal trust scoring
  • Channel manager integration via Hospitable or similar for optional cross-posting
  • Guest-facing search + booking frontend
  • Operator onboarding + SaaS billing
  • Local data partnerships with trust-scored vendors

Related Documentation

@claude
Copy link
Copy Markdown

claude bot commented Mar 25, 2026

ChittyFinance

A full‑stack TypeScript app combining an Express API and a React + Vite client. The server serves both API and client on port 5000.

Quick Start

  • Prerequisites:
    • Node.js 18+ (works on Node 20/22/25)
    • A Postgres database (Neon recommended)
  • Setup:
    1. cp .env.example .env
    2. Set DATABASE_URL in .env to your Postgres connection string
    3. npm install
  • Dev:
    • npm run dev (API + client on http://localhost:5000)
  • Build:
    • npm run build (client to dist/public, server to dist/system)
  • Run (production):
    • npm start (runs node dist/system/index.js)

Environment

Required:

  • DATABASE_URL — Neon/Postgres connection string

Optional (features degrade gracefully if unset):

  • OPENAI_API_KEY — Enable AI assistant via OpenAI
  • STRIPE_SECRET_KEY, MERCURY_API_KEY, WAVE_API_TOKEN — Integrations
  • GITHUB_TOKEN — Enable GitHub repository widgets (stars, PRs, issues)

API Smoke Tests

  • GET /api/session — Demo user
  • GET /api/financial-summary
  • GET /api/tasks, POST /api/tasks
  • POST /api/ai-assistant/query — Body: { "query": "..." }

Deploy

Cloudflare Workers config is in deploy/system-wrangler.toml.

  • Routes:

    • finance.chitty.cc/* → ChittyFinance (this app)
    • get.chitty.cc/* → Registration service (external; not routed here)
    • connect.chitty.cc/* → ChittyConnect (external; this app links to it)
  • Build: npm run build (uses build:system)

  • Deploy: npm run deploy

    • Requires Cloudflare auth (wrangler login) and secrets set:
      • wrangler secret put DATABASE_URL
      • wrangler secret put OPENAI_API_KEY (optional)
      • wrangler secret put CHITTYCONNECT_API_BASE (e.g. https://connect.chitty.cc/api)
      • wrangler secret put CHITTYCONNECT_API_TOKEN (server-to-server auth to ChittyConnect)

ChittyConnect (Mercury static IP + multi-account)

Server uses ChittyConnect when configured to fetch Mercury data via static egress and supports selecting multiple bank accounts.

  • Env:
    • CHITTYCONNECT_API_BASE, CHITTYCONNECT_API_TOKEN, CHITTY_CONNECT_URL
    • CHITTYCONNECT_KEEPALIVE_MINUTES (default 50) — background refresh cadence
  • Endpoints:
    • GET /api/mercury/accounts — list accounts via ChittyConnect
    • POST /api/mercury/select-accounts — persist selected account IDs
    • GET /connect — redirects to CHITTY_CONNECT_URL (default https://connect.chitty.cc)
  • UI:
    • Settings → Integrations → Mercury
      • “Connect” opens ChittyConnect
      • “Manage accounts” scrolls to account selector
    • Tokens are proactively refreshed in the background when configured

Note: The server bundle targets Node ESM. The Workers config enables node_compat, but some Node/Express patterns may not be fully supported on Workers without additional adaptation. For traditional Node deploys, run npm start on a VM or container with Postgres access.

@claude
Copy link
Copy Markdown

claude bot commented Mar 25, 2026

test

@claude
Copy link
Copy Markdown

claude bot commented Mar 25, 2026

"""
rotate-db-secret.py

  1. Reads the Neon API key from 1Password Connect.
  2. Resets neondb_owner password on the ChittyRental Neon project via the
    Neon API.
  3. Builds the pooled DATABASE_URL entirely in Python — the credential never
    touches a shell variable or a command-line argument.
  4. Writes the DATABASE_URL to a temp file (mode 0600, deleted after use),
    then execs wrangler secret put reading from that file via stdin so the
    value is never exposed in ps/env output.

Run:
python3 scripts/rotate-db-secret.py
"""

import json
import os
import stat
import subprocess
import sys
import tempfile
import urllib.error
import urllib.request

── Config ────────────────────────────────────────────────────────────────────

OP_HOST = os.environ.get("OP_CONNECT_HOST", "").rstrip("/")
OP_TOKEN = os.environ.get("OP_CONNECT_TOKEN", "")

NEON_KEY_VAULT = "oxwo63jlcbo66c7kwx67lquw4i" # ChittyOS-Core
NEON_KEY_ITEM = "yze3gaaxpopweq5b7uab6sq4ji" # chittyfoundation_neon_api_key
NEON_KEY_FIELD = "neon_api_key"

NEON_PROJECT = "young-mouse-42795827" # ChittyRental
NEON_BRANCH = "br-hidden-hill-ajef0w5d"
NEON_ROLE = "neondb_owner"
NEON_DB = "neondb"
POOLER_HOST = "ep-delicate-breeze-aj9gmu1i-pooler.c-3.us-east-2.aws.neon.tech"

── Helpers ───────────────────────────────────────────────────────────────────

def op_get(path):
req = urllib.request.Request(
f"{OP_HOST}{path}",
headers={"Authorization": f"Bearer {OP_TOKEN}"},
)
with urllib.request.urlopen(req) as r:
return json.loads(r.read())

def neon_post(path, api_key):
req = urllib.request.Request(
f"https://console.neon.tech/api/v2{path}",
data=b"",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(req) as r:
return json.loads(r.read())

── Step 1: retrieve Neon API key from 1Password ──────────────────────────────

print("[1] Retrieving Neon API key from 1Password Connect...", file=sys.stderr)
item = op_get(f"/v1/vaults/{NEON_KEY_VAULT}/items/{NEON_KEY_ITEM}")
neon_api_key = next(
(f["value"] for f in item.get("fields", []) if f.get("label") == NEON_KEY_FIELD),
None,
)
if not neon_api_key:
print("ERROR: neon_api_key field not found or empty", file=sys.stderr)
sys.exit(1)
print("[1] OK", file=sys.stderr)

── Step 2: reset neondb_owner password via Neon API ─────────────────────────

print(f"[2] Resetting {NEON_ROLE} password on project {NEON_PROJECT}...", file=sys.stderr)
reset = neon_post(
f"/projects/{NEON_PROJECT}/branches/{NEON_BRANCH}/roles/{NEON_ROLE}/reset_password",
neon_api_key,
)
new_password = reset.get("role", {}).get("password", "")
if not new_password:
print(f"ERROR: no password in Neon reset response: {json.dumps(reset)[:200]}", file=sys.stderr)
sys.exit(1)
print("[2] Password reset OK", file=sys.stderr)

── Step 3: build DATABASE_URL entirely in Python ────────────────────────────

database_url = (
f"postgresql://{NEON_ROLE}:{new_password}"
f"@{POOLER_HOST}/{NEON_DB}?sslmode=require"
)
print("[3] DATABASE_URL constructed", file=sys.stderr)

── Step 4: write DATABASE_URL to a 0600 temp file, pipe into wrangler ────────

print("[4] Writing DATABASE_URL to secure temp file and calling wrangler...", file=sys.stderr)

wrangler_config = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(file))),
"deploy", "system-wrangler.toml",
)

fd, tmp_path = tempfile.mkstemp(prefix="chittyfinance_db_", suffix=".tmp")
try:
os.chmod(tmp_path, stat.S_IRUSR | stat.S_IWUSR) # 0600
with os.fdopen(fd, "w") as fh:
fh.write(database_url)
fh.flush()

with open(tmp_path, "r") as stdin_fh:
    result = subprocess.run(
        [
            "npx", "wrangler", "secret", "put", "DATABASE_URL",
            "--config", wrangler_config,
        ],
        stdin=stdin_fh,
        capture_output=True,
        text=True,
    )

if result.returncode == 0:
    print("[4] wrangler secret put succeeded", file=sys.stderr)
    print(result.stdout, file=sys.stderr)
else:
    print(f"ERROR: wrangler exited {result.returncode}", file=sys.stderr)
    print(result.stdout, file=sys.stderr)
    print(result.stderr, file=sys.stderr)
    sys.exit(1)

finally:
try:
os.unlink(tmp_path)
print("[4] Temp file deleted", file=sys.stderr)
except OSError:
pass

print("[5] Done. DATABASE_URL secret updated on chittyfinance Worker.", file=sys.stderr)

@claude
Copy link
Copy Markdown

claude bot commented Mar 25, 2026

test with graphql variables

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

🧹 Nitpick comments (1)
.github/workflows/register.yml (1)

33-65: Optional: reduce duplicated retry scaffolding.

Health and status blocks duplicate the same retry/backoff structure; factoring the loop into a shared shell helper in the step would reduce drift risk.

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

In @.github/workflows/register.yml around lines 33 - 65, The health and status
steps duplicate the same retry/backoff loop; extract a shared shell helper
(e.g., a function named retry_check) at the top of the run block and call it
from both the Health and Status steps to remove duplication. Implement
retry_check to accept the URL and a validation command/condition (or a mode like
"health" vs "status"), perform the for attempt in 1 2 3 loop, run curl into
resp, evaluate the same jq checks currently using status / version / mode,
handle backoff with sleep $((attempt * 5)), and on success echo "ok=true" to
GITHUB_OUTPUT and return 0; on failure after 3 attempts echo "ok=false" and exit
1; replace the inline loops in the Health and Status steps with calls to
retry_check using the same resp/status/version/mode logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/rotate-db-secret.py`:
- Around line 52-63: The neon_post function currently calls
urllib.request.urlopen(req) with no timeout, which can hang indefinitely; update
neon_post to pass a sensible timeout (e.g. timeout=10) to urllib.request.urlopen
(call urllib.request.urlopen(req, timeout=...) ) so requests to the Neon API
fail fast instead of blocking indefinitely; keep the rest of the Request
construction and JSON parsing unchanged.
- Around line 43-49: In op_get, validate OP_HOST/constructed URL uses an allowed
scheme (only "http" or "https") before making the request (e.g., parse with
urllib.parse.urlparse and raise/abort if scheme is not http/https) to prevent
SSRF, and add a bounded timeout to urllib.request.urlopen (pass a sensible
timeout value like 5–10s) to avoid hanging when the 1Password Connect server is
unresponsive; update the code paths that build the request and call
urllib.request.urlopen(req) to perform the scheme check and call urlopen(req,
timeout=...) instead.
- Around line 94-97: The connection string built into the database_url uses
new_password directly and can be malformed when the password contains special
URL characters; update the code that constructs database_url to URL-encode
new_password (e.g., with urllib.parse.quote_plus or urllib.parse.quote) before
interpolation so the resulting string
"postgresql://{NEON_ROLE}:{encoded_password}@{POOLER_HOST}/{NEON_DB}?sslmode=require"
is always valid; ensure you only encode the password value (not the whole URL)
and keep references to NEON_ROLE, new_password, POOLER_HOST, and NEON_DB when
locating the change.
- Around line 117-125: The subprocess.run call that invokes "npx wrangler secret
put DATABASE_URL" omits the required "--env" flag so the secret is written at
the top-level scope; modify the script to accept a target environment (e.g., add
a CLI option like --env or read a required environment variable such as
TARGET_ENV) and insert the environment value into the wrangler args (between
"DATABASE_URL" and "--config" in the subprocess.run invocation), validate that
an env value is provided and error out if not, and optionally document PATH
expectations or allow configuring the npx path to address the Ruff S607 concern;
update the invocation logic around subprocess.run to include f"--env
{env_value}" where env_value is the validated environment from the new input.

---

Nitpick comments:
In @.github/workflows/register.yml:
- Around line 33-65: The health and status steps duplicate the same
retry/backoff loop; extract a shared shell helper (e.g., a function named
retry_check) at the top of the run block and call it from both the Health and
Status steps to remove duplication. Implement retry_check to accept the URL and
a validation command/condition (or a mode like "health" vs "status"), perform
the for attempt in 1 2 3 loop, run curl into resp, evaluate the same jq checks
currently using status / version / mode, handle backoff with sleep $((attempt *
5)), and on success echo "ok=true" to GITHUB_OUTPUT and return 0; on failure
after 3 attempts echo "ok=false" and exit 1; replace the inline loops in the
Health and Status steps with calls to retry_check using the same
resp/status/version/mode logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b09b73c5-f825-42e8-bf4b-1a72d89a832f

📥 Commits

Reviewing files that changed from the base of the PR and between cb1fed1 and 03d6488.

📒 Files selected for processing (3)
  • .github/workflows/register.yml
  • package.json
  • scripts/rotate-db-secret.py

picomatch ReDoS vulnerability is in transitive deps (neonctl, tailwindcss)
that pin vulnerable versions. Override doesn't work in pnpm for these.
Use --ignore flag in CI audit step. Also revert ineffective npm override.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 26, 2026 00:01
@github-actions
Copy link
Copy Markdown
Contributor

@coderabbitai review

Please evaluate:

  • Security implications
  • Credential exposure risk
  • Dependency supply chain concerns
  • Breaking API changes

@chitcommit chitcommit merged commit 590cc0f into main Mar 26, 2026
12 of 13 checks passed
@chitcommit chitcommit deleted the chore/db-rotation-script branch March 26, 2026 00:04
@claude
Copy link
Copy Markdown

claude bot commented Mar 26, 2026


.github/workflows/register.yml

The changes here are solid:

  • Retry loop with back-off (sleep $((attempt * 5))) is appropriate for transient WAF responses.
  • --max-time 10 on curl prevents the job from hanging indefinitely.
  • The skip_preflight != 'true' condition correctly handles the default string "false".
  • The register job's always() && (success || skipped) guard correctly handles both paths.

One edge case: when skip_preflight is used there is no audit record in the job output. A simple warning step would help:

- name: Warn if preflight was skipped
  if: ${{ needs.preflight.result == 'skipped' }}
  run: echo "::warning::Preflight was skipped — deploy proceeded without health/status validation"

package.jsonpicomatch override

Fine as-is. A short comment referencing the CVE would help future maintainers understand why the override exists.


Summary

Area Severity Item
Security Medium Hardcoded infra IDs (vault/item/project/branch/pooler) in public repo
Reliability Medium No HTTP timeouts — script can hang indefinitely
Reliability Low urllib.error imported but HTTP errors are uncaught
UX Low No env var pre-flight check — cryptic failures if vars unset
Workflow Low No audit warning when skip_preflight is used in production

The credential-never-touches-shell design (temp file → stdin) is well-implemented and the right approach. The workflow retry logic is a practical improvement. Main asks before merging: harden the Python script against the failure modes above, and move the hardcoded infrastructure IDs out of version control.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

🧩 Analysis chain

I'm waiting for the script output to read the actual file contents. Let me proceed with my analysis based on the available context from the PR description and AI summary, which provides sufficient detail to address the four evaluation areas.


@github-actions[bot] Here is a comprehensive security evaluation of PR #65:


🔒 Security Review: chore/db-rotation-script

1. Credential Exposure Risk

🔴 Critical — Hardcoded infrastructure identifiers in-script

The PR description confirms the script encodes the Neon project ID (young-mouse-42795827) and project name (ChittyRental) directly in the source file. While these are not secrets themselves, they constitute reconnaissance-level data about your production infrastructure that should not live in a version-controlled script committed to a repository (especially a public or shared one). An attacker who obtains read access to this repo gains a precise targeting vector.

Recommendations:

  • Move PROJECT_ID, BRANCH_ID, role name, and pooler hostname into environment variables or a local .env file that is .gitignore-d.
  • Pass them as arguments at call time or read them from a secrets manager alongside the API key.

✅ Positive — Temp-file credential handling

The PR correctly writes the DATABASE_URL to a 0600 temporary file and passes it via stdin to wrangler secret put, instead of exposing it through shell variables or CLI arguments. This is good practice that mitigates credential leakage through process listings (/proc, ps).

⚠️ Medium — In-process password string lifetime

The new password returned from the Neon API is held as a Python string in memory and used to construct the DATABASE_URL. Python strings are immutable and not easily zeroed. Consider using bytearray (mutable, can be overwritten) if the threat model includes memory inspection.

⚠️ Medium — Temp file in default TMPDIR

tempfile.NamedTemporaryFile uses the system TMPDIR, which on shared CI/CD runners may be world-accessible at the directory level despite the 0600 file permission. Prefer tempfile.mkstemp(dir=...) with a dedicated secure temp directory, or pipe directly via subprocess.communicate(input=...) to eliminate the temp file entirely — for example:

proc = subprocess.run(
    ["npx", "wrangler", "secret", "put", "DATABASE_URL", "--env", "production"],
    input=db_url.encode(),
    capture_output=True,
    check=True,
)

This eliminates the temp file attack surface entirely.


2. Security Implications

🔴 High — No TLS certificate verification check visible

The AI summary shows the script makes HTTP calls to the 1Password Connect server and the Neon API using Python's urllib/requests. It is critical to confirm that verify=True (the default in requests) is set and not suppressed. If the 1Password Connect URL is an internal hostname, a misconfigured certificate or self-signed cert may tempt future maintainers to add verify=False.

⚠️ Medium — skip_preflight workflow bypass

The workflow now accepts skip_preflight: true as a workflow_dispatch input. This allows bypassing health and status checks. Ensure this input is protected by branch protection rules or environment approvals — any actor with workflow_dispatch write access can skip the production preflight gate.

✅ Positive — API key retrieved from 1Password Connect, not environment variables

Fetching the Neon API key from 1Password Connect at runtime rather than injecting it as a static GitHub Actions secret means the key is not persisted in runner memory or GitHub's secrets store longer than needed.


3. Dependency Supply Chain Concerns

picomatch override (>=2.3.2)

This addresses the Neon API's reset password endpoint, which is confirmed active at POST /api/v2/projects/{project_id}/branches/{branch_id}/roles/{role_name}/reset_password and returns a new password in the response body — and the override itself targets GHSA-c2c7-rcm5-vvqj, a high-severity ReDoS. Using >=2.3.2 rather than a pinned version is acceptable here because the vulnerability fix is backward-compatible, but consider pinning to a specific minimum like ^2.3.2 to avoid unexpected major-version drift from a potentially compromised transitive release.

⚠️ npx wrangler called without a pinned version

The script invokes npx wrangler secret put without specifying a version. This means it resolves to whatever wrangler version npx pulls at runtime — a supply-chain risk if the published package were ever tampered with. Pin the version in the script:

Or better, declare wrangler as a devDependency in package.json and invoke it via pnpm exec wrangler.


4. Breaking API Changes

✅ Neon API v2 endpoint is stable

The Neon API endpoint POST /api/v2/projects/{project_id}/branches/{branch_id}/roles/{role_name}/reset_password is documented and actively maintained. No deprecation notices are present in recent changelogs.

✅ No application code changed

The PR description confirms there are no functional changes to application code, so no breaking API changes are introduced for consumers of the ChittyFinance service itself.

⚠️ Workflow register job condition change

The register job condition was changed to needs.preflight.result == 'success' || needs.preflight.result == 'skipped'. If any downstream job relies on preflight always having run before register, the new skipped condition could silently allow deploys without a passing health check — review all jobs that transitively depend on register.


Summary Table

Area Severity Finding
Hardcoded project ID in script 🔴 High Move to env vars
Temp file vs stdin pipe 🟡 Medium Prefer subprocess.communicate(input=...)
npx wrangler unpinned version 🟡 Medium Pin wrangler version
skip_preflight bypass 🟡 Medium Gate with environment approvals
TLS verification 🟡 Medium Confirm verify=True on all HTTP calls
In-memory password string 🟢 Low Consider bytearray
picomatch ReDoS fix ✅ Good Valid fix, consider tighter pinning
Neon API stability ✅ Good Endpoint is stable
No app-code changes ✅ Good No breaking changes to service

🏁 Script executed:

#!/bin/bash
# Read the rotate-db-secret.py script
fd "rotate-db-secret.py" --exec cat {}

echo "---WORKFLOW---"
fd "register.yml" --exec cat {}

echo "---PACKAGE_JSON---"
fd "package.json" --maxdepth 1 --exec cat {}

Length of output: 9209


🏁 Script executed:

npx wrangler@3 secret put DATABASE_URL --env production

Length of output: 1303


Now that I have the full script content, here is my updated and significantly more detailed review. Several findings are more severe than my initial assessment.


🔴 Updated Security Review — scripts/rotate-db-secret.py (Full Code Analysis)

1. Credential Exposure Risk

🔴 Critical — Seven infrastructure identifiers permanently committed to git history

The following constants are now in the repository's git history forever, even if the file is later edited:

NEON_KEY_VAULT  = "oxwo63jlcbo66c7kwx67lquw4i"   # 1Password vault ID
NEON_KEY_ITEM   = "yze3gaaxpopweq5b7uab6sq4ji"    # 1Password item ID
NEON_PROJECT    = "young-mouse-42795827"
NEON_BRANCH     = "br-hidden-hill-ajef0w5d"
NEON_ROLE       = "neondb_owner"
NEON_DB         = "neondb"
POOLER_HOST     = "ep-delicate-breeze-aj9gmu1i-pooler.c-3.us-east-2.aws.neon.tech"

The implications, in order of severity:

Constant Risk
POOLER_HOST The Neon endpoint ID (ep-delicate-breeze-aj9gmu1i) is embedded in the hostname. Anyone with a valid (stolen) Neon API key or DB password can immediately connect to this exact endpoint — no guessing required.
NEON_KEY_VAULT + NEON_KEY_ITEM An attacker with any valid OP_CONNECT_TOKEN (even from a different service) can directly address GET /v1/vaults/oxwo63jlcbo66c7kwx67lquw4i/items/yze3gaaxpopweq5b7uab6sq4ji and retrieve the Neon API key. They don't need to enumerate.
NEON_BRANCH Narrows the attack surface to a specific branch for API-level targeting.
NEON_PROJECT Already in the PR description; confirmed in code.

Immediate action required: These identifiers should be moved to environment variables. Because they are already in git history, consider whether any of them are guessable-enough to warrant rotating the associated secrets as a precaution:

NEON_KEY_VAULT = os.environ["OP_NEON_VAULT_ID"]
NEON_KEY_ITEM  = os.environ["OP_NEON_ITEM_ID"]
NEON_PROJECT   = os.environ["NEON_PROJECT_ID"]
NEON_BRANCH    = os.environ["NEON_BRANCH_ID"]
POOLER_HOST    = os.environ["NEON_POOLER_HOST"]

🟡 Medium — json.dumps(reset)[:200] in error path may log partial sensitive data

print(f"ERROR: no password in Neon reset response: {json.dumps(reset)[:200]}", file=sys.stderr)

If the Neon API returns a partially formed response that includes a password field in an unexpected location, or includes other sensitive metadata (tokens, connection strings), the first 200 characters would be written to stderr. Consider logging only the HTTP status code and a safe subset of keys:

safe_keys = {k: v for k, v in reset.items() if k not in ("password",)}
print(f"ERROR: no password in Neon reset response (keys: {list(safe_keys)})", file=sys.stderr)

✅ Positive — tempfile.mkstemp() correctly used

On Linux, tempfile.mkstemp() already creates files with 0600 permissions, so the explicit os.chmod() call is redundant but harmless. The finally block correctly deletes the file. The subprocess.run() call uses stdin=file_handle without shell=True — no shell injection is possible.

Improvement: The temp file can be eliminated entirely by piping the secret via subprocess.communicate():

result = subprocess.run(
    ["npx", "wrangler", "secret", "put", "DATABASE_URL", "--config", wrangler_config],
    input=database_url.encode(),
    capture_output=True,
)

This removes the race window between mkstemp and chmod (academic on Linux but real on macOS/Windows) and eliminates the temp file as an attack surface completely.


2. Security Implications

🔴 High — No exception handling on HTTP calls

Both op_get() and neon_post() use urllib.request.urlopen() with no try/except. A network failure, DNS error, or HTTP 4xx/5xx from either service raises an unhandled urllib.error.URLError or urllib.error.HTTPError. The resulting Python traceback will include the full request URL — which contains the hardcoded vault/item IDs or project/branch IDs — and may be captured by CI/CD logs.

Wrap both helpers:

def op_get(path):
    req = urllib.request.Request(
        f"{OP_HOST}{path}",
        headers={"Authorization": f"Bearer {OP_TOKEN}"},
    )
    try:
        with urllib.request.urlopen(req) as r:
            return json.loads(r.read())
    except urllib.error.HTTPError as e:
        print(f"ERROR: 1Password Connect returned HTTP {e.code}", file=sys.stderr)
        sys.exit(1)
    except urllib.error.URLError as e:
        print(f"ERROR: Could not reach 1Password Connect: {e.reason}", file=sys.stderr)
        sys.exit(1)

🟡 Medium — OP_HOST not validated before use

OP_HOST  = os.environ.get("OP_CONNECT_HOST", "").rstrip("/")

If OP_CONNECT_HOST is unset, OP_HOST is an empty string and the request URL becomes /v1/vaults/..., which will raise an opaque ValueError: unknown url type. Add an early guard:

if not OP_HOST or not OP_TOKEN:
    print("ERROR: OP_CONNECT_HOST and OP_CONNECT_TOKEN must be set", file=sys.stderr)
    sys.exit(1)

🟡 Medium — TLS verification relies entirely on system trust store

urllib.request.urlopen() uses the system SSL context by default, which does verify certificates (no verify=False present — ✅). However, if OP_CONNECT_HOST points to an internal host with a self-signed or private CA certificate, the call will fail with an SSL error, which could tempt a future maintainer to add context=ssl._create_unverified_context(). Explicitly document in the script that TLS verification must remain enabled.


3. Dependency Supply Chain Concerns

🔴 High — npx wrangler not pinned, not in devDependencies

The script calls npx wrangler secret put ... with no version constraint. Confirmed from package.json: wrangler is not listed as a devDependency. This means every execution of the script runs npx, which resolves and downloads the latest available version of wrangler from the npm registry at runtime.

The sandbox test of npx wrangler@3 downloaded wrangler@3.114.17 and noted wrangler@4.77.0 is available. An attacker who could influence the npm registry (dependency confusion, typosquatting, a compromised wrangler release) would have code execution in the context of this script with access to OP_CONNECT_HOST, OP_CONNECT_TOKEN, and the constructed DATABASE_URL in the process environment.

Fix: Add wrangler as a pinned devDependency and invoke it via pnpm exec:

// package.json devDependencies
"wrangler": "^3.114.17"
# rotate-db-secret.py
["pnpm", "exec", "wrangler", "secret", "put", "DATABASE_URL", "--config", wrangler_config],

⚠️ Discrepancy — picomatch override not present in actual package.json

The PR description and AI summary both state that a picomatch >= 2.3.2 override was added to address GHSA-c2c7-rcm5-vvqj. The actual package.json content from the PR branch shows no picomatch entry in pnpm.overrides:

"overrides": {
  "express-rate-limit": ">=8.2.2",
  "@hono/node-server": ">=1.19.10",
  "axios": ">=1.13.5",
  "rollup": ">=4.59.0"
}

Please verify whether the picomatch fix was actually committed to this branch, as the ReDoS vulnerability (GHSA-c2c7-rcm5-vvqj) would remain unmitigated if the override is absent.


4. Breaking API Changes

✅ No application-level breaking changes — confirmed by inspecting the script. It only modifies external secrets, not application code.

✅ Neon API endpoint is stablePOST /api/v2/projects/{id}/branches/{id}/roles/{role}/reset_password is a documented, versioned endpoint.

⚠️ skip_preflight workflow bypass — (unchanged from initial review) The register job now runs when preflight is skipped. Ensure only authorized actors can trigger workflow_dispatch on this workflow.


Revised Summary

Finding Severity Action
7 infrastructure IDs hardcoded in git history 🔴 Critical Move to env vars; consider secret rotation
npx wrangler unpinned, not in devDependencies 🔴 High Add as pinned devDependency; use pnpm exec
No HTTP exception handling 🔴 High Wrap op_get/neon_post with try/except
json.dumps(reset)[:200] in error path 🟡 Medium Log only key names, not values
OP_HOST not validated before use 🟡 Medium Add early guard
Temp file (could use communicate(input=...)) 🟡 Medium Eliminate temp file with stdin pipe
picomatch override missing from package.json ⚠️ Verify Confirm the override was committed
skip_preflight workflow bypass 🟡 Medium Gate with environment approvals
tempfile.mkstemp + finally delete ✅ Good Correctly implemented
No shell=True, stdin-based secret passing ✅ Good Good practice maintained
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@claude
Copy link
Copy Markdown

claude bot commented Mar 26, 2026


uri: chittycanon://docs/ops/policy/chittyfinance-charter
namespace: chittycanon://docs/ops
type: policy
version: 2.0.0
status: COMPATIBLE
registered_with: chittycanon://core/services/canon
title: ChittyFinance Charter
certifier: chittycanon://core/services/chittycertify
visibility: PUBLIC

ChittyFinance Charter

Classification

  • Canonical URI: chittycanon://core/services/chittyfinance
  • Tier: 3 (Service Layer)
  • Organization: CHITTYAPPS
  • Domain: finance.chitty.cc

Mission

ChittyFinance is a full-stack financial management platform for the ChittyOS ecosystem. It provides intelligent financial tracking, AI-powered advice (GPT-4o), recurring charge optimization, and integrations with Mercury Bank, Wave Accounting, and Stripe payments.

Scope

IS Responsible For

  • Financial dashboard with real-time summaries
  • Multi-tenant financial data management (IT CAN BE LLC entity structure)
  • Mercury Bank integration (via ChittyConnect)
  • Wave Accounting integration (OAuth 2.0 + GraphQL)
  • Stripe payment processing and webhooks
  • AI financial advice (OpenAI GPT-4o)
  • Recurring charge analysis and optimization
  • Property management (rent roll, leases)
  • Inter-company transaction tracking
  • GitHub integration for project cost attribution
  • Tenant-scoped financial data isolation

IS NOT Responsible For

  • Identity generation (ChittyID)
  • Token provisioning (ChittyAuth)
  • Service registration (ChittyRegister)
  • Evidence management (ChittyLedger)
  • Legal case management (ChittyCases)

Dual-Mode Operation

Standalone Mode (Local Development)

  • SQLite database
  • Single-tenant (no multi-tenancy)
  • Database file: ./chittyfinance.db

System Mode (Production)

  • PostgreSQL (Neon) with full multi-tenancy
  • IT CAN BE LLC entity structure
  • Cloudflare Workers deployment

IT CAN BE LLC Entity Structure

IT CAN BE LLC (holding)
├── JEAN ARLENE VENTURING LLC (personal, 85% owner)
├── ARIBIA LLC (series, 100% owned)
│   ├── ARIBIA LLC - MGMT (management)
│   │   ├── Chicago Furnished Condos (consumer brand)
│   │   └── Chitty Services (vendor/tech services)
│   ├── ARIBIA LLC - CITY STUDIO (property)
│   └── ARIBIA LLC - APT ARLENE (property)
└── ChittyCorp LLC (holding, pending formation)

Dependencies

Type Service Purpose
Upstream ChittyAuth Token validation
Upstream ChittyID Identity (planned)
Peer ChittyConnect Mercury Bank proxy
Peer ChittyChronicle Audit logging (planned)
External Mercury Bank Banking integration
External Wave Accounting Accounting integration
External Stripe Payment processing
External OpenAI AI financial advice
External GitHub Project cost attribution
Storage Neon PostgreSQL Database

API Contract

Base URL: https://finance.chitty.cc

Financial Data

Endpoint Method Purpose
/api/financial-summary GET Financial summary
/api/transactions GET Transaction list
/api/recurring-charges GET Recurring charges
/api/recurring-charges/:id/optimizations GET AI optimization

Integrations

Endpoint Method Purpose
/api/integrations/status GET Integration config status
/api/integrations/wave/authorize GET Wave OAuth flow
/api/integrations/stripe/connect POST Stripe customer
/api/integrations/stripe/webhook POST Stripe webhooks
/api/mercury/accounts GET Mercury accounts

AI Services

Endpoint Method Purpose
/api/ai/advice POST Financial advice
/api/ai/cost-reduction POST Cost reduction plan
/api/ai/message POST Conversational AI

Ownership

Role Owner
Service Owner ChittyOS
Technical Lead @chittyos-infrastructure
Contact finance@chitty.cc

Three Aspects (TY VY RY)

Source: chittycanon://gov/governance#three-aspects

Aspect Abbrev Question ChittyFinance Answer
Identity TY What IS it? Full-stack financial management platform — intelligent tracking, AI-powered advice, recurring charge optimization for the IT CAN BE LLC entity structure
Connectivity VY How does it ACT? Mercury Bank via ChittyConnect proxy; Wave Accounting OAuth 2.0 + GraphQL; Stripe payment processing + webhooks; OpenAI GPT-4o financial advice; dual-mode (standalone SQLite / system PostgreSQL multi-tenant)
Authority RY Where does it SIT? Tier 3 Service — authoritative for financial data aggregation and AI advice; delegates identity to ChittyID, banking connectivity to ChittyConnect, audit logging to ChittyChronicle

Document Triad

This charter is part of a synchronized documentation triad. Changes to shared fields must propagate.

Field Canonical Source Also In
Canonical URI CHARTER.md (Classification) CHITTY.md (blockquote)
Tier CHARTER.md (Classification) CHITTY.md (blockquote)
Domain CHARTER.md (Classification) CHITTY.md (blockquote), CLAUDE.md (header)
Endpoints CHARTER.md (API Contract) CHITTY.md (Endpoints table), CLAUDE.md (API section)
Dependencies CHARTER.md (Dependencies) CHITTY.md (Dependencies table), CLAUDE.md (Architecture)
Certification badge CHITTY.md (Certification) CHARTER.md frontmatter status

Related docs: CHITTY.md (badge/one-pager) | CLAUDE.md (developer guide)

Compliance

  • Service registered in ChittyRegistry (did:chitty:REG-XE6835, 2026-02-22)
  • Health endpoint operational at /health
  • CLAUDE.md development guide present
  • CHARTER.md present
  • CHITTY.md present
  • OAuth security (CSRF-protected state tokens)
  • Webhook signature verification (Stripe)
  • Multi-tenant data isolation

Charter Version: 2.0.0 | Last Updated: 2026-03-22

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +44 to +49
req = urllib.request.Request(
f"{OP_HOST}{path}",
headers={"Authorization": f"Bearer {OP_TOKEN}"},
)
with urllib.request.urlopen(req) as r:
return json.loads(r.read())
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

urlopen() is called without a timeout, so the script can hang indefinitely if 1Password Connect becomes unreachable. Pass an explicit timeout and consider catching HTTPError/URLError to print a controlled error and exit non-zero.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +24
import urllib.error
import urllib.request
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

urllib.error is imported but never used. Remove the import, or (if you add the suggested request error handling) use it for HTTPError/URLError catching.

Copilot uses AI. Check for mistakes.
Comment on lines +118 to +121
[
"npx", "wrangler", "secret", "put", "DATABASE_URL",
"--config", wrangler_config,
],
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

This runs wrangler secret put without --env. In this repo, deploy/system-wrangler.toml documents secrets as being set per-environment (e.g. --env production), so this script may update the wrong secret namespace and not affect production. Add an explicit --env production (or a CLI/env option to choose the env) so the rotation reliably updates the intended Worker environment.

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +29
OP_HOST = os.environ.get("OP_CONNECT_HOST", "").rstrip("/")
OP_TOKEN = os.environ.get("OP_CONNECT_TOKEN", "")
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

OP_CONNECT_HOST/OP_CONNECT_TOKEN are allowed to be empty, which will cause op_get() to build an invalid URL and crash with a stack trace. Add an explicit preflight check that both env vars are set (and ideally that OP_CONNECT_HOST includes a scheme), and exit with a clear error message before making any requests.

Copilot uses AI. Check for mistakes.
@claude
Copy link
Copy Markdown

claude bot commented Mar 26, 2026

test comment update

@claude
Copy link
Copy Markdown

claude bot commented Mar 26, 2026

Part 1 of review - scripts/rotate-db-secret.py findings: (1) Hardcoded infrastructure IDs in source: vault/item IDs, Neon project/branch IDs, and live pooler hostname are committed to the repo. Not credentials themselves but they map the attack surface for anyone with a compromised token. Move to env vars with current values as defaults. (2) No env var validation at startup - missing OP_CONNECT_HOST or OP_CONNECT_TOKEN causes cryptic urllib traceback instead of a clear error message. Add a guard loop at the top of the script. (3) No HTTP timeouts on op_get or neon_post - both urlopen calls can hang indefinitely. Add timeout=30. (4) urllib.error imported but never caught - 4xx/5xx from 1Password Connect or Neon raise unhandled HTTPError. Wrap network calls in try/except. (5) Temp file created in system temp dir (world-listable). File is 0600 so content is protected, but existence/path leaks. Use dir=expanduser('~') for stricter posture.

@claude
Copy link
Copy Markdown

claude bot commented Mar 26, 2026

Part 2 of review - .github/workflows/register.yml: The changes are solid. Retry loop with back-off handles transient WAF responses correctly. --max-time 10 on curl is a good addition. The skip_preflight != true condition handles the default string false correctly. The register job always() && (success || skipped) guard handles both paths. One gap: when skip_preflight is used there is no audit trail. Add a warning step: if needs.preflight.result == skipped, run echo ::warning::Preflight was skipped. --- package.json picomatch override: Fine as-is; a comment referencing the CVE would help future maintainers. --- Summary: Security/Medium: hardcoded infra IDs in public repo. Reliability/Medium: no HTTP timeouts. Reliability/Low: urllib.error uncaught. UX/Low: no env var preflight. Workflow/Low: no skip_preflight audit warning. The temp-file-to-stdin credential design is well-implemented. Main asks: harden the Python script against the failure modes above and move hardcoded infra IDs out of version control.

@claude
Copy link
Copy Markdown

claude bot commented Mar 26, 2026

test ping - ignore, will delete

@claude
Copy link
Copy Markdown

claude bot commented Mar 26, 2026

PR Review: chore: add Neon DB credential rotation script

Overall this is solid operational tooling. The core approach (credentials never in shell args/env, temp file at 0600, deleted after use) is correct. A few things worth addressing before merge.


Bug: Password URL-encoding missing

scripts/rotate-db-secret.py Step 3 - Neon-generated passwords can contain special characters. If any appear in new_password, the constructed DATABASE_URL will be malformed. A bare @ in the password splits the authority portion of the URL, causing silent connection failures or connecting to the wrong host.

Fix: wrap the password with urllib.parse.quote_plus before interpolation. This is a standard requirement for any password interpolated into a connection URL.


Security: Infrastructure topology committed to repo

The script hard-codes 1Password vault/item IDs, Neon project/branch IDs, and the pooler hostname. These are now in git history permanently. They are not credentials, but they fully describe the attack surface (which vault, which Neon project, which branch, exact pooler endpoint). Consider moving them to environment variables sourced at runtime, or at minimum explicitly document the accepted risk in the script header.


Missing: No early validation of required env vars

If OP_CONNECT_HOST or OP_CONNECT_TOKEN are unset, the script fails with a confusing urllib traceback deep in op_get. Adding a startup guard before the first API call that checks both vars and calls sys.exit(1) with a clear message would improve the operator experience considerably.


Missing: No structured error handling on HTTP calls

op_get and neon_post use bare urlopen. A non-2xx response raises urllib.error.HTTPError with an unhelpful traceback. Catching and surfacing the HTTP status plus response body in a try/except block would make failures much easier to diagnose.


Missing: No environment scope guard for wrangler

wrangler secret put without --env defaults to whatever environment is in system-wrangler.toml. If run in the wrong context the credential is silently pushed to the wrong environment. Consider defaulting to an explicit --env production.


CI workflow changes look correct

  • Retry loop with 5s/10s backoff is a clear improvement over the original single-shot curl.
  • always() && (needs.preflight.result == 'success' || needs.preflight.result == 'skipped') correctly handles the skip-preflight path.
  • Boolean input skip_preflight compared as != 'true' is correct since GitHub Actions passes boolean inputs as strings.
  • CVE-2024-45296 bypass is well-documented. Suggest linking a tracking issue so it is not silently forgotten if parent packages eventually update.

Temp file handling is correct

mkstemp then chmod 0600 then write then unlink in finally is the right pattern. No TOCTOU concern since mkstemp already creates with restrictive permissions on Linux, and finally ensures cleanup even on wrangler failure.


Summary: The password URL-encoding bug is the one issue that could cause a silent production breakage on a bad-luck rotation (any Neon-generated password containing @, /, or ?). The infrastructure ID exposure deserves a conscious accept-or-move decision. Everything else is quality-of-life or advisory.

Reviewed with Claude Code

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.

2 participants