# QuickVoice Audit

## Executive Summary

- Repository: `/home/ubuntu/rahul/quickvoice`
- Commit: `d0284076a776a80bc39f06d90d56b4c650e366ac`
- Started: `2026-06-19T06:22:07+00:00`
- Finished: `2026-06-19T06:58:32+00:00`
- Module session status: 6 passed

This report was generated by `scripts/codex_module_audit.py`, which runs one isolated Codex audit session per module and aggregates the final module reports.

## Module Status

| Module | Status | Return Code | Duration | Logs |
| --- | --- | ---: | ---: | --- |
| `root-tooling` | passed | 0 | 448.7s | `.audit_runs/2026-06-19T062207+0000/root-tooling.stdout.jsonl`, `.audit_runs/2026-06-19T062207+0000/root-tooling.stderr.log` |
| `apps-web` | passed | 0 | 412.6s | `.audit_runs/2026-06-19T062207+0000/apps-web.stdout.jsonl`, `.audit_runs/2026-06-19T062207+0000/apps-web.stderr.log` |
| `apps-console` | passed | 0 | 360.3s | `.audit_runs/2026-06-19T062207+0000/apps-console.stdout.jsonl`, `.audit_runs/2026-06-19T062207+0000/apps-console.stderr.log` |
| `apps-server` | passed | 0 | 317.7s | `.audit_runs/2026-06-19T062207+0000/apps-server.stdout.jsonl`, `.audit_runs/2026-06-19T062207+0000/apps-server.stderr.log` |
| `apps-ai` | passed | 0 | 410.4s | `.audit_runs/2026-06-19T062207+0000/apps-ai.stdout.jsonl`, `.audit_runs/2026-06-19T062207+0000/apps-ai.stderr.log` |
| `packages-config` | passed | 0 | 236.0s | `.audit_runs/2026-06-19T062207+0000/packages-config.stdout.jsonl`, `.audit_runs/2026-06-19T062207+0000/packages-config.stderr.log` |

## Findings By Module

### root-tooling: Root tooling, CI, and developer experience

- Status: `passed`
- Return code: `0`
- Duration: `448.7s`
- Stdout log: `.audit_runs/2026-06-19T062207+0000/root-tooling.stdout.jsonl`
- Stderr log: `.audit_runs/2026-06-19T062207+0000/root-tooling.stderr.log`

## Summary
Audited root-tooling at commit `d0284076a776a80bc39f06d90d56b4c650e366ac`. Highest risk areas are broken local env bootstrap, missing PR build/test gates, and a security audit workflow that passes despite known high advisories. No tracked files were changed by the audit.

## Critical/High Findings
- **High: Local development bootstrap is broken.** `scripts/dev-env.sh:19-23` copies `apps/server/.env.dev.example`, `apps/console/.env.dev.example`, and `apps/web/.env.dev.example`, but those files are absent. Repro: `./scripts/dev-env.sh` fails with `cp: cannot stat ... apps/server/.env.dev.example`; `node --test tests/dev-orchestration.test.mjs` also fails at `tests/dev-orchestration.test.mjs:49-60`. Impact: `task env:dev` and `task up:dev` cannot reliably onboard developers. Fix: add the missing templates or stop copying them, and preflight all sources before creating any destination files.

- **High: PRs are not gated by build, lint, typecheck, or tests.** `server-build.yml:3-14` and `ai-build.yml:3-9` run only on `push` to `main` or manual dispatch. The only PR workflow is `security-audit.yml:6-7`, and it does not run `package.json:12-17` scripts. Impact: broken app code can merge, and Docker image failures are discovered after merge. Fix: add a required PR CI workflow running frozen install, lint, typecheck, builds, JS tests, and Python tests; make deploy workflows depend on that signal.

- **High: Security audit passes while high vulnerabilities exist.** `.github/workflows/security-audit.yml:31-32` uses `pnpm audit --audit-level critical`. Repro: that command exited `0` while reporting `105 vulnerabilities found` including `47 high`; `pnpm audit --audit-level moderate` exited `1`. Examples include high advisories for `next`, `axios`, `minimatch`, `flatted`, and `protobufjs`. Fix: fail at least on `high`, triage dev-only vs prod paths, and track suppressions explicitly.

## Medium Findings
- **Medium: Root quality scripts have coverage gaps.** `pnpm check-types` only runs `server#check-types`; Turborepo dry-run showed `console#check-types` and `web#check-types` as `<NONEXISTENT>`. `pnpm lint` skips the server because `apps/server/package.json:10-17` has no `lint` script. Fix: add missing package scripts and make CI fail on missing expected tasks.

- **Medium: Dev Postgres is exposed on all host interfaces with static credentials.** `docker-compose.dev.yml:8-13` publishes `5432:5432` with `quickvoice/quickvoice`. Impact: on many Docker hosts this is reachable beyond localhost. Fix: bind `127.0.0.1:5432:5432` and document dev-only credentials.

- **Medium: Local Docker dependencies omit Redis.** `docker-compose.dev.yml:3-24` only starts Postgres, while `apps/server/src/config/redis.ts:3` defaults to `redis://localhost:6379` and BullMQ uses it at `apps/server/src/queues/kb.queue.ts:22-23`. Impact: KB queue paths fail in local dev unless Redis is installed separately. Fix: add a Redis service and `REDIS_URL` env template entry.

- **Medium: Deploy workflows publish mutable `latest` without promotion controls.** `server-build.yml:51-55` and `ai-build.yml:46-50` tag every main build as both SHA and `latest`; neither workflow has `environment`, `concurrency`, smoke tests, image scan, or rollback metadata. Fix: use protected environments, immutable promotion tags, concurrency, and post-build validation.

## Low Findings
- **Low: Mixed lockfiles undermine pnpm dependency hygiene.** `package.json:24` declares `pnpm@9.0.0`, but tracked npm lockfiles also exist; root `package-lock.json:8-14` even pins `better-auth` differently from `package.json:28-30`. Fix: keep one authoritative lockfile strategy and remove stale package-manager locks.

- **Low: Local dependency install can rewrite the lockfile.** `scripts/dev-node-deps.sh:24` runs plain `pnpm install`. Fix: default to `pnpm install --frozen-lockfile` and add an explicit update task for lockfile changes.

## UI/UX Issues
- **Dev startup prints the wrong AI API URL.** Root `.env.dev.example:8-9` sets `AI_API_PORT=8000`, but `apps/ai/.env.dev.example:2` sets `5555`; `scripts/dev-up.sh:53-58` sources the AI env in the child process while `scripts/dev-up.sh:83` prints the root value. Impact: users are told to open `:8000` while the service can run on `:5555`. Fix: make one port source authoritative.

## Missing Features
- No required PR workflow for root `build`, `lint`, `check-types`, JS tests, Python tests, and Docker build validation.
- No root aggregate `test` or `ci` script; `package.json:17` only exposes `test:dev-orchestration`.
- Dependabot only covers npm at `/` (`.github/dependabot.yml:3-4`); it does not cover GitHub Actions, Docker images, or `apps/ai/requirements.txt`.
- No SBOM, image signing/provenance, or container vulnerability scanning in deploy workflows.

## New Feature Opportunities
- Add `task ci` / `pnpm ci:local` to run the same checks as required PR CI.
- Add `task doctor` checks for missing env templates, port conflicts, Redis availability, and Docker Compose health.
- Add Docker Compose profiles for optional local services such as Redis and local object-storage/mail substitutes.

## Checks Run
- `git rev-parse HEAD` matched the requested commit.
- `bash -n scripts/dev-clear-processes.sh scripts/dev-doctor.sh scripts/dev-env.sh scripts/dev-node-deps.sh scripts/dev-up.sh` passed.
- `docker compose -f docker-compose.dev.yml --env-file .env.dev.example config` passed.
- `task --list` passed.
- `node --test tests/dev-orchestration.test.mjs` failed as described.
- `node --test tests/kb-worker-config.test.mjs` passed.
- `pnpm audit --audit-level critical` exited `0`; `pnpm audit --audit-level moderate` exited `1`.
- `pnpm dlx turbo@2.8.20 run {check-types,lint,build} --dry=json` used for task graph inspection only.

## Blocked Or Unverified
- Full install/build/lint/typecheck were not run because `node_modules` is absent and `pnpm install` would create repository files.
- Docker image builds and AWS/ECR deploy paths were not executed because they require external credentials and push side effects.
- Docker services were not started because that would create containers/volumes.
- Worktree had pre-existing changes: modified `.gitignore`, untracked `scripts/codex_module_audit.py`, and untracked `tests/test_codex_module_audit.py`.

### apps-web: Marketing website and public UX

- Status: `passed`
- Return code: `0`
- Duration: `412.6s`
- Stdout log: `.audit_runs/2026-06-19T062207+0000/apps-web.stdout.jsonl`
- Stderr log: `.audit_runs/2026-06-19T062207+0000/apps-web.stderr.log`

## Summary
Audited `apps/web` at commit `d0284076a776a80bc39f06d90d56b4c650e366ac`. No files were modified. Main risks are broken conversion paths, future-dated content being publicly indexed, missing public assets, and inconsistent compliance/pricing claims.

## Critical/High Findings
- **High: Contact form submit path is broken.**
  Proven: [contact-us-form-section.tsx:96](/home/ubuntu/rahul/quickvoice/apps/web/src/components/landing/contact-us/contact-us-form-section.tsx:96) posts to `/api/contact`, but `apps/web/src/app` has no API route. Repro: submit `/company/contact`; request 404s and likely falls into the generic error path.
  Fix: add `app/api/contact/route.ts`, use a server action, or replace the form with a working external lead-capture flow.

- **High: Public CTAs link to nonexistent routes.**
  Proven: `/use-cases/appointment-scheduling` renders `/contact` and `/demo` links at [appointment-scheduling-cta-section.tsx:60](/home/ubuntu/rahul/quickvoice/apps/web/src/components/landing/appointment-scheduling/appointment-scheduling-cta-section.tsx:60) and [68](/home/ubuntu/rahul/quickvoice/apps/web/src/components/landing/appointment-scheduling/appointment-scheduling-cta-section.tsx:68); `/use-cases/customer-support` does the same at [customer-support-cta-section.tsx:58](/home/ubuntu/rahul/quickvoice/apps/web/src/components/landing/customer-support/customer-support-cta-section.tsx:58). Those routes do not exist.
  Fix: use `/company/contact` and `DEMO_BOOKING_URL`.

- **High: Future-dated blog posts are published and treated as latest.**
  Proven: `getAllPosts()` returns all markdown without filtering dates at [blog.ts:48](/home/ubuntu/rahul/quickvoice/apps/web/src/lib/blog.ts:48), and the blog index selects the last sorted post as “Latest Article” at [blog/page.tsx:96](/home/ubuntu/rahul/quickvoice/apps/web/src/app/blog/page.tsx:96). Static scan found 36 posts dated after June 19, 2026, up to [week-52:4](/home/ubuntu/rahul/quickvoice/apps/web/content/blog/week-52-ultimate-ai-voice-agent-glossary.md:4).
  Fix: filter future dates unless explicitly previewing/scheduling.

## Medium Findings
- **Missing social and visible image assets.**
  `/og-image.png` is referenced across many pages, e.g. [blog/page.tsx:19](/home/ubuntu/rahul/quickvoice/apps/web/src/app/blog/page.tsx:19), but is not in `public/`. Visible `next/image` sources such as [careers-hero-section.tsx:192](/home/ubuntu/rahul/quickvoice/apps/web/src/components/landing/careers/careers-hero-section.tsx:192) and [operations-automation-why-section.tsx:102](/home/ubuntu/rahul/quickvoice/apps/web/src/components/landing/operations-automation/operations-automation-why-section.tsx:102) also point to missing `/images/...` files.
  Impact: broken page imagery and broken social previews.

- **Compliance and pricing copy contradict each other.**
  Pricing says BAA is Enterprise-only at [pricing/page.tsx:201](/home/ubuntu/rahul/quickvoice/apps/web/src/app/pricing/page.tsx:201), while the HIPAA page says every healthcare customer gets a BAA at [hipaa/page.tsx:53](/home/ubuntu/rahul/quickvoice/apps/web/src/app/compliance/hipaa/page.tsx:53) and [419](/home/ubuntu/rahul/quickvoice/apps/web/src/app/compliance/hipaa/page.tsx:419).
  Fix: align legal/commercial wording before publishing.

- **Free-trial allowance is inconsistent.**
  Pricing says Free includes 15 minutes/month at [pricing/page.tsx:31](/home/ubuntu/rahul/quickvoice/apps/web/src/app/pricing/page.tsx:31), but homepage FAQ says up to 100 minutes at [faq-2.tsx:89](/home/ubuntu/rahul/quickvoice/apps/web/src/components/mvpblocks/faq-2.tsx:89).
  Impact: trust and support friction.

- **Desktop dropdown navigation is hover-only.**
  Dropdowns open via mouse enter/leave at [header-1.tsx:162](/home/ubuntu/rahul/quickvoice/apps/web/src/components/mvpblocks/header-1.tsx:162), with parent links set to `#` at [header-1.tsx:27](/home/ubuntu/rahul/quickvoice/apps/web/src/components/mvpblocks/header-1.tsx:27).
  Impact: keyboard users cannot reliably access nested navigation.

## Low Findings
- **Nested landmarks and duplicate `main-content`.**
  Layout wraps every page in `<main id="main-content">` at [layout.tsx:91](/home/ubuntu/rahul/quickvoice/apps/web/src/app/layout.tsx:91), while pages also render `<main>`, including duplicate `id="main-content"` on the homepage at [page.tsx:145](/home/ubuntu/rahul/quickvoice/apps/web/src/app/page.tsx:145).
  Fix: keep the landmark/id in one place.

- **Manifest icons 404.**
  [manifest.ts:15](/home/ubuntu/rahul/quickvoice/apps/web/src/app/manifest.ts:15) references `/icon` and [manifest.ts:20](/home/ubuntu/rahul/quickvoice/apps/web/src/app/manifest.ts:20) references `/apple-icon`, but no matching files/routes exist.

- **Global preload is applied to every route.**
  [layout.tsx:62](/home/ubuntu/rahul/quickvoice/apps/web/src/app/layout.tsx:62) preloads `/dashboard.png` globally, although it is homepage-specific.
  Fix: move preload/priority handling to the component/page that needs it.

## UI/UX Issues
- Several visible buttons are inert: “Watch Our Story” at [careers-hero-section.tsx:165](/home/ubuntu/rahul/quickvoice/apps/web/src/components/landing/careers/careers-hero-section.tsx:165), “View All Benefits” at [careers-benefits-section.tsx:187](/home/ubuntu/rahul/quickvoice/apps/web/src/components/landing/careers/careers-benefits-section.tsx:187), and “Apply Now” at [careers-job-opportunities-section.tsx:193](/home/ubuntu/rahul/quickvoice/apps/web/src/components/landing/careers/careers-job-opportunities-section.tsx:193).

- FAQ accordion buttons lack `aria-expanded` / `aria-controls`, e.g. [faq-2.tsx:237](/home/ubuntu/rahul/quickvoice/apps/web/src/components/mvpblocks/faq-2.tsx:237).

- Missing images create blank/broken visual areas on Careers and Operations Automation pages.

- `SearchAction` points to `https://quickvoice.co/blog?q=...` at [page.tsx:35](/home/ubuntu/rahul/quickvoice/apps/web/src/app/page.tsx:35), but the blog page has no search handling.

## Missing Features
- Working contact/demo lead submission backend.
- Content publish-state handling for draft/scheduled blog posts.
- Centralized CTA route validation for `/register`, `/login`, demo, and contact URLs.
- Accessible keyboard-first dropdown/accordion components.

## New Feature Opportunities
- Per-page OG image generation or a real shared `/og-image.png`.
- Blog search/filter UI to match the declared `SearchAction`.
- Conversion analytics for contact/demo/signup CTAs.
- Scheduled content workflow with preview mode and sitemap exclusion.

## Checks Run
- `git rev-parse HEAD` confirmed `d0284076a776a80bc39f06d90d56b4c650e366ac`.
- `git status --short` showed unrelated dirty files outside `apps/web`; untouched.
- Static file/route inspection with `find`, `rg`, `nl`.
- Node frontmatter scan: 90 blog posts, 36 future-dated after June 19, 2026.
- Asset inventory: only `dashboard.png`, `earth.webp`, logos, `robots.txt`, and `llms.txt` exist in `apps/web/public`.

## Blocked Or Unverified
- `pnpm --filter web lint` blocked: `eslint` not found; dependencies not installed.
- `pnpm --filter web exec tsc --noEmit --incremental false` blocked: `tsc` not found.
- Did not run `next build`, `next dev`, or rendered browser QA because those would create `.next/` artifacts, violating the no-create rule.
- External services and production env were not verified: `NEXT_PUBLIC_CONSOLE_URL`, TidyCal, analytics, and compliance certification claims.

### apps-console: Authenticated console UX and frontend logic

- Status: `passed`
- Return code: `0`
- Duration: `360.3s`
- Stdout log: `.audit_runs/2026-06-19T062207+0000/apps-console.stdout.jsonl`
- Stderr log: `.audit_runs/2026-06-19T062207+0000/apps-console.stderr.log`

## Summary
Audited `apps/console` at commit `d0284076a776a80bc39f06d90d56b4c650e366ac`. No files under `apps/console` were modified.

Main risks: onboarding can navigate to an invalid org after create failure, phone-number buying sends the wrong provider value, roles/access-control UI is not actually wired, and several data pages render failed API loads as empty/zero states.

## Critical/High Findings
- **Proven: create-organization failure still shows success and navigates to `/orgs/undefined`.**
  [CreateOrg.tsx:43](/home/ubuntu/rahul/quickvoice/apps/console/src/components/forms/orgs/CreateOrg.tsx:43) handles `error` but does not return or throw; [CreateOrg.tsx:51](/home/ubuntu/rahul/quickvoice/apps/console/src/components/forms/orgs/CreateOrg.tsx:51) still toasts success and [CreateOrg.tsx:52](/home/ubuntu/rahul/quickvoice/apps/console/src/components/forms/orgs/CreateOrg.tsx:52) pushes `data?.id`. Repro with duplicate/invalid org slug. Fix by returning/throwing on error, requiring `data.id`, and not clearing the error in `finally`.

- **Proven: buy-number flow sends upper-case provider values despite lower-case console API types.**
  `TelephonyProvider` is `"twilio" | "telnyx"` in [types.ts:6](/home/ubuntu/rahul/quickvoice/apps/console/src/lib/api/types.ts:6), but the form uses `"TWILIO" | "TELNYX"` in [BuyNumberDrawer.tsx:44](/home/ubuntu/rahul/quickvoice/apps/console/src/components/numbers/BuyNumberDrawer.tsx:44) and casts that runtime value at [BuyNumberDrawer.tsx:70](/home/ubuntu/rahul/quickvoice/apps/console/src/components/numbers/BuyNumberDrawer.tsx:70). Impact: search/buy requests can fail or hit wrong provider handling. Use lower-case form values or transform before API calls.

- **Proven: roles page is a UI shell, not functional role management.**
  [roles/page.tsx:61](/home/ubuntu/rahul/quickvoice/apps/console/src/app/(app)/settings/roles/page.tsx:61) notes wiring is follow-up; [roles/page.tsx:63](/home/ubuntu/rahul/quickvoice/apps/console/src/app/(app)/settings/roles/page.tsx:63) hardcodes `roles` to `[]`, and delete has no handler at [roles/page.tsx:216](/home/ubuntu/rahul/quickvoice/apps/console/src/app/(app)/settings/roles/page.tsx:216). Impact: custom access control cannot be viewed or managed. Wire list/create/delete/update to the auth API with real loading/error states.

## Medium Findings
- **Proven: multiple API failures render as empty or zero data.**
  Examples: dashboard ignores `isError` and shows `0` values at [dashboard/page.tsx:22](/home/ubuntu/rahul/quickvoice/apps/console/src/app/(app)/dashboard/page.tsx:22), agents defaults to `[]` at [agents/page.tsx:11](/home/ubuntu/rahul/quickvoice/apps/console/src/app/(app)/agents/page.tsx:11), calls shows “No calls match” after query failure at [CallsTable.tsx:263](/home/ubuntu/rahul/quickvoice/apps/console/src/components/calls/CallsTable.tsx:263), and KB does the same at [kb/page.tsx:13](/home/ubuntu/rahul/quickvoice/apps/console/src/app/(app)/kb/page.tsx:13). Add explicit `isError` states with retry.

- **Proven: enabled webhooks can be saved with blank URLs.**
  Schema allows `""` at [WebhooksTab.tsx:34](/home/ubuntu/rahul/quickvoice/apps/console/src/components/agents/tabs/WebhooksTab.tsx:34), then submit builds enabled webhook objects at [WebhooksTab.tsx:78](/home/ubuntu/rahul/quickvoice/apps/console/src/components/agents/tabs/WebhooksTab.tsx:78). Add conditional validation: URL required when enabled.

- **Proven code risk: MCP setup opens external pages without `noopener`.**
  [mcp.ts:13](/home/ubuntu/rahul/quickvoice/apps/console/src/hooks/queries/mcp.ts:13) uses `window.open(setupUrl, "_blank")`; setting `opener = null` after open at [mcp.ts:19](/home/ubuntu/rahul/quickvoice/apps/console/src/hooks/queries/mcp.ts:19) is weaker than opening with `noopener,noreferrer`. Use the third `window.open` argument and validate setup URLs.

- **Proven: agent template picker is ignored.**
  `selectedTemplate` is maintained at [NewAgentDialog.tsx:80](/home/ubuntu/rahul/quickvoice/apps/console/src/components/agents/NewAgentDialog.tsx:80), but submit always sends `templateId: null` at [NewAgentDialog.tsx:89](/home/ubuntu/rahul/quickvoice/apps/console/src/components/agents/NewAgentDialog.tsx:89). Send the selected template or remove the picker.

## Low Findings
- **Proven: “Forgot password?” links to a missing route.**
  [login-form.tsx:119](/home/ubuntu/rahul/quickvoice/apps/console/src/components/forms/auth/login-form.tsx:119) links `/forgot-password`, but no matching app route exists.

- **Proven: registration has weak submit/error handling.**
  [register-form.tsx:40](/home/ubuntu/rahul/quickvoice/apps/console/src/components/forms/auth/register-form.tsx:40) has no `try/finally`, logs auth errors at [register-form.tsx:47](/home/ubuntu/rahul/quickvoice/apps/console/src/components/forms/auth/register-form.tsx:47), and the submit button is not disabled while loading at [register-form.tsx:157](/home/ubuntu/rahul/quickvoice/apps/console/src/components/forms/auth/register-form.tsx:157).

- **Proven: tool parameter/header validation permits empty keys and names.**
  [ToolSheet.tsx:41](/home/ubuntu/rahul/quickvoice/apps/console/src/components/tools/ToolSheet.tsx:41) uses bare `z.string()` for key/value and param fields. Add minimum validation or filter blank rows before submit.

## UI/UX Issues
- Mobile sidebar hides the sheet close button at [sidebar.tsx:189](/home/ubuntu/rahul/quickvoice/apps/console/src/components/ui/sidebar.tsx:189), and nav links in [NavMain.tsx:52](/home/ubuntu/rahul/quickvoice/apps/console/src/components/shell/NavMain.tsx:52) do not close the mobile sheet.
- Several dense tables use `overflow-hidden` instead of horizontal scroll, e.g. [CallsTable.tsx:304](/home/ubuntu/rahul/quickvoice/apps/console/src/components/calls/CallsTable.tsx:304), [AgentsTable.tsx:136](/home/ubuntu/rahul/quickvoice/apps/console/src/components/agents/AgentsTable.tsx:136), [KbTable.tsx:151](/home/ubuntu/rahul/quickvoice/apps/console/src/components/kb/KbTable.tsx:151).
- Destructive member removal has no confirmation at [organization/page.tsx:383](/home/ubuntu/rahul/quickvoice/apps/console/src/app/(app)/settings/organization/page.tsx:383).
- Several icon-only controls lack accessible labels, including [ToolCard.tsx:90](/home/ubuntu/rahul/quickvoice/apps/console/src/components/tools/ToolCard.tsx:90) and detach buttons in [ToolsTab.tsx:100](/home/ubuntu/rahul/quickvoice/apps/console/src/components/agents/tabs/ToolsTab.tsx:100).

## Missing Features
- Password reset/forgot-password screens and handlers.
- Real custom role listing/edit/delete and member role changes.
- Agent delete is disabled in both table/detail flows.
- Per-agent limits tab exists but is not included in `AgentTabs`; current page only says roadmap.
- Billing usage is a TODO at [billing/page.tsx:202](/home/ubuntu/rahul/quickvoice/apps/console/src/app/(app)/settings/billing/page.tsx:202).
- Campaign outbound UI is absent despite `campaignSchema` at [campaign.ts:3](/home/ubuntu/rahul/quickvoice/apps/console/src/models/outbound/campaign.ts:3).

## New Feature Opportunities
- Audit log for org settings, API keys, members, roles, billing, and destructive actions.
- Agent test-call/simulation flow; table currently has disabled “Test”.
- Pending invites management, ownership transfer, and role reassignment.
- Mobile card views for calls, agents, KB, and numbers instead of squeezed tables.
- Billing usage forecasts and overage warnings.

## Checks Run
- `git rev-parse HEAD` -> `d0284076a776a80bc39f06d90d56b4c650e366ac`
- `git diff -- apps/console --stat` -> no output
- Static inspection with `rg`, `find`, `sed`, and `nl -ba`
- `pnpm --dir apps/console lint` -> blocked: `eslint: not found`, `node_modules` missing
- `pnpm --dir apps/console exec tsc --noEmit -p tsconfig.json` -> blocked: `tsc` not found

## Blocked Or Unverified
- Lint, typecheck, build, and browser/a11y verification were not completed because dependencies are not installed.
- API contract assumptions beyond console-local types were not verified against live services or credentials.
- Existing dirty worktree entries are outside `apps/console` and were not touched.

### apps-server: API server, auth, data model, and integrations

- Status: `passed`
- Return code: `0`
- Duration: `317.7s`
- Stdout log: `.audit_runs/2026-06-19T062207+0000/apps-server.stdout.jsonl`
- Stderr log: `.audit_runs/2026-06-19T062207+0000/apps-server.stderr.log`

## Summary
Audited `apps/server` at `d0284076a776a80bc39f06d90d56b4c650e366ac` read-only. Main risks: API keys bypass RBAC, KB creation can write with caller-supplied tenant context, and webhook/tool “secret” values are stored/returned as plaintext.

## Critical/High Findings
- **High: API keys bypass all per-route RBAC.**
  Evidence: valid API keys become `authMethod: "apiKey"` in [auth.middleware.ts:96](/home/ubuntu/rahul/quickvoice/apps/server/src/middleware/auth.middleware.ts:96), then `requirePermission` returns without checking the requested permission in [authorize.middleware.ts:69](/home/ubuntu/rahul/quickvoice/apps/server/src/middleware/authorize.middleware.ts:69).
  Repro: use any valid org API key against mutating routes such as `POST /api/v1/numbers` or `DELETE /api/v1/calls/:id`; the route permission is skipped.
  Impact: leaked/low-scope API keys can mutate org resources and trigger paid telephony/MCP actions.
  Fix: verify API-key permissions/scopes against `permissions`, or map API keys to a user/member role and call the same permission engine.

- **High: KB creation trusts caller-supplied `organizationId` and `userId`.**
  Evidence: controller passes raw body in [kb.controller.ts:10](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/kb/kb.controller.ts:10); schema requires client `userId`/`organizationId` in [kb.schema.ts:39](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/kb/kb.schema.ts:39); repository writes them directly in [kb.repository.ts:14](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/kb/kb.repository.ts:14).
  Repro: authenticated org A admin posts `/api/v1/kb` with `organizationId` or `agentId` from another org.
  Impact: cross-tenant KB rows, queue jobs, and `knowledgeSourcesCount` corruption.
  Fix: inject `req.auth.activeOrganizationId`/`req.auth.userId`, remove those body fields, and verify `agentId` belongs to the active org in the same transaction.

- **High: integration secrets are stored and returned as normal JSON/plaintext.**
  Evidence: webhook fields accept `{ value, type: "Secret" }` in [agent.schema.ts:50](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/agent/agent.schema.ts:50), then are persisted unchanged in [agent.repository.ts:69](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/agent/agent.repository.ts:69) and returned by config reads in [agent.controller.ts:80](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/agent/agent.controller.ts:80). Tool headers/body are JSON fields in [schema.prisma:536](/home/ubuntu/rahul/quickvoice/apps/server/prisma/schema.prisma:536); `Secret.value` is also plain `String` at [schema.prisma:653](/home/ubuntu/rahul/quickvoice/apps/server/prisma/schema.prisma:653).
  Impact: DB compromise or read-capable org members can recover API keys/webhook secrets.
  Fix: envelope-encrypt secrets, store references not values, redact reads, and resolve secrets only at execution time.

## Medium Findings
- **Validation output is discarded.** `validate()` calls `schema.parse(req.body)` but never assigns parsed data in [validate.middleware.ts:10](/home/ubuntu/rahul/quickvoice/apps/server/src/middleware/validate.middleware.ts:10). Zod defaults, transforms, and stripped unknown keys are not applied unless routes parse inline, as outbound does in [outbound-call.route.ts:38](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/outbound/outbound-call.route.ts:38). Fix by assigning `req.body = schema.parse(req.body)`.

- **Outbound LiveKit flow can leave orphan side effects.** A DB row is created, then LiveKit dispatch is created before SIP participant creation in [outbound-call.service.ts:86](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/outbound/outbound-call.service.ts:86) and [outbound-call.service.ts:106](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/outbound/outbound-call.service.ts:106); catch only marks failed in [outbound-call.service.ts:134](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/outbound/outbound-call.service.ts:134). Add cleanup/idempotency.

- **PII and tool data are retained without redaction controls.** Call transcripts are stored raw in [calllog.repository.ts:46](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/calllogs/calllog.repository.ts:46) while `isPiiRedacted` defaults false in [schema.prisma:467](/home/ubuntu/rahul/quickvoice/apps/server/prisma/schema.prisma:467). MCP arguments/results are preview-logged in [mcp.service.ts:552](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/mcp/mcp.service.ts:552). Add retention/redaction policies.

- **Tool counters can drift.** Attach always increments `toolsCount` in [tool.repository.ts:75](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/tools/tool.repository.ts:75); detach decrements without proving the relation exists in [tool.repository.ts:93](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/tools/tool.repository.ts:93). Recount from relations or conditionally update.

- **SSRF risk in URL-bearing integrations.** Tool `api_url` accepts any URL in [tool.schema.ts:20](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/tools/tool.schema.ts:20); webhooks accept any URL in [agent.schema.ts:47](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/agent/agent.schema.ts:47); MCP custom URL blocking only covers localhost literals in [mcp.service.ts:63](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/mcp/mcp.service.ts:63). Block private/link-local ranges after DNS resolution.

## Low Findings
- Rate limiting runs after `express.json()` in [index.ts:81](/home/ubuntu/rahul/quickvoice/apps/server/src/index.ts:81), so body parsing work happens before throttling.
- Error middleware returns raw `err.message` for 500s in [error.middleware.ts:8](/home/ubuntu/rahul/quickvoice/apps/server/src/middleware/error.middleware.ts:8), risking internal detail leaks.
- Telnyx number search logs provider response data in [phone.service.ts:63](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/numbers/phone.service.ts:63); trunk updates also log debug state in [phone.service.ts:210](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/numbers/phone.service.ts:210).

## UI/UX Issues
- Public Swagger docs expose internal auth schemes and persist auth in browser storage: [index.ts:32](/home/ubuntu/rahul/quickvoice/apps/server/src/index.ts:32), [swagger.ts:60](/home/ubuntu/rahul/quickvoice/apps/server/src/config/swagger.ts:60).
- Swagger tells users to provide KB `userId` and `organizationId`, matching the security bug: [swagger.ts:270](/home/ubuntu/rahul/quickvoice/apps/server/src/config/swagger.ts:270).
- Error responses are inconsistent: rate limit returns `{ success, message }`, global errors return `{ message }`.

## Missing Features
- No observed server-side enforcement of plan minutes/quotas from [plans.ts:1](/home/ubuntu/rahul/quickvoice/apps/server/data/plans.ts:1).
- No Stripe metered usage reporting found for call minutes/costs.
- No retention job for transcripts, recordings, MCP logs, or KB data.
- KB delete TODOs leave S3/vector cleanup unimplemented in [kb.service.ts:38](/home/ubuntu/rahul/quickvoice/apps/server/src/modules/kb/kb.service.ts:38).
- Number release/delete route is commented out.

## New Feature Opportunities
- Org-scoped audit log for API-key use, config changes, telephony purchases, MCP execution, and internal calls.
- Central secret manager with rotation, redaction, and execution-time resolution.
- Per-org rate limits and quota dashboards tied to billing plans.
- Readiness checks for DB, Redis, S3, Stripe, Twilio/Telnyx, LiveKit, and Smithery.

## Checks Run
- `git rev-parse HEAD` confirmed `d0284076a776a80bc39f06d90d56b4c650e366ac`.
- `git status --short` checked before/after; existing `.gitignore` modification and untracked audit helper files were not touched.
- Static inspection with `rg`, `find`, `sed`, and `nl`.
- `pnpm --dir apps/server check-types` attempted.

## Blocked Or Unverified
- Typecheck blocked: `tsc: not found`; `apps/server/node_modules` is missing. I did not install dependencies because that would write files.
- Tests/builds not run for the same dependency reason.
- DB, Redis, Stripe, Twilio, Telnyx, LiveKit, S3, Inngest, and Smithery integration behavior was not exercised due missing credentials/services.
- Inngest endpoint auth/signature behavior remains unverified.

### apps-ai: Python AI service, LiveKit worker, and RAG runtime

- Status: `passed`
- Return code: `0`
- Duration: `410.4s`
- Stdout log: `.audit_runs/2026-06-19T062207+0000/apps-ai.stdout.jsonl`
- Stderr log: `.audit_runs/2026-06-19T062207+0000/apps-ai.stderr.log`

## Summary

Audited `apps/ai` at `d0284076a776a80bc39f06d90d56b4c650e366ac` in read-only mode. No `apps/ai` files were changed. Highest risk areas are internal API auth, KB URL/file ingestion, call finalization, RAG failure behavior, and PII-heavy logging.

## Critical/High Findings

- **High: unauthenticated FastAPI config proxy can leak runtime agent config.** [api.py:30](/home/ubuntu/rahul/quickvoice/apps/ai/api.py:30) exposes `GET /agents/{agent_id}/config` without `_verify_internal`, while [config_handler.py:52](/home/ubuntu/rahul/quickvoice/apps/ai/handlers/config_handler.py:52) calls the backend with the internal bearer key. Repro: run AI API with `SERVER_API_URL` and `INTERNAL_API_KEY`, then request `/agents/<id>/config` without auth. Impact: system prompts, variables, webhooks, and MCP connection metadata can be exposed. Fix: require internal auth on this route and fail startup if the key is absent outside explicit dev mode.

- **High: KB ingestion permits SSRF and unbounded server-side downloads.** [kb_handler.py:39](/home/ubuntu/rahul/quickvoice/apps/ai/handlers/kb_handler.py:39) and [kb_handler.py:49](/home/ubuntu/rahul/quickvoice/apps/ai/handlers/kb_handler.py:49) fetch caller-provided `url`/`presignedUrl` with redirects and no scheme, host, IP, content-type, or size restrictions. Combined with optional auth in [api.py:17](/home/ubuntu/rahul/quickvoice/apps/ai/api.py:17), this can hit metadata services/internal hosts or ingest huge files into Gemini/Pinecone. Fix: fail-closed auth, block private/link-local IPs, allowlist schemes/hosts where possible, stream with byte limits, and cap chunks/embeddings per job.

## Medium Findings

- **Call logs can be dropped during shutdown.** Finalization only runs from `participant_disconnected` and is launched via `asyncio.create_task` in [main.py:281](/home/ubuntu/rahul/quickvoice/apps/ai/main.py:281). There is no `ctx.add_shutdown_callback`, so SIGTERM, worker recycle, network disconnects, or early job shutdown can skip transcripts/log posting. Fix: make finalization idempotent, register it with LiveKit job shutdown, await it, and retry via a durable queue.

- **Transcript timestamps are wrong for LiveKit messages.** [livekit_handler.py:40](/home/ubuntu/rahul/quickvoice/apps/ai/handlers/livekit_handler.py:40) passes `msg.created_at`, which LiveKit documents as a float timestamp, but [calllog_handler.py:103](/home/ubuntu/rahul/quickvoice/apps/ai/handlers/calllog_handler.py:103) only preserves strings or `datetime`; floats become “now”. Reproduced locally: a `1704067200.0` transcript timestamp became the current audit time. Fix: handle numeric epoch seconds.

- **Re-indexing shorter KB documents leaves stale Pinecone chunks.** [kb_handler.py:162](/home/ubuntu/rahul/quickvoice/apps/ai/handlers/kb_handler.py:162) uses stable IDs `kbId#i` and [kb_handler.py:176](/home/ubuntu/rahul/quickvoice/apps/ai/handlers/kb_handler.py:176) only upserts. Old higher-index chunks remain after a shorter replacement, so RAG can cite deleted content. Fix: delete by `kbId` before re-upsert or version documents and filter current versions.

- **RAG provider failures silently degrade into no-context answers.** [rag_handler.py:60](/home/ubuntu/rahul/quickvoice/apps/ai/handlers/rag_handler.py:60) catches all exceptions and returns `""`; [main.py:147](/home/ubuntu/rahul/quickvoice/apps/ai/main.py:147) then proceeds without a user-visible failure. Impact: agents can hallucinate when KB is unavailable. Fix: distinguish “no matches” from “retrieval failed”, emit metrics, and inject/return a clear KB-unavailable signal.

- **PII and sensitive runtime data are logged at INFO.** Full config payloads, call context, message objects, and transcripts are logged at [config_handler.py:59](/home/ubuntu/rahul/quickvoice/apps/ai/handlers/config_handler.py:59), [main.py:222](/home/ubuntu/rahul/quickvoice/apps/ai/main.py:222), and [livekit_handler.py:30](/home/ubuntu/rahul/quickvoice/apps/ai/handlers/livekit_handler.py:30). [logger.py:24](/home/ubuntu/rahul/quickvoice/apps/ai/utils/logger.py:24) also enables diagnostic trace detail. Fix: redact phone numbers, transcripts, prompts, headers, webhook URLs, and disable `diagnose=True` in production.

- **MCP execution lacks AI-side guardrails.** The LLM can call [main.py:187](/home/ubuntu/rahul/quickvoice/apps/ai/main.py:187) with arbitrary arguments; server-side org/agent attachment checks reduce cross-tenant risk, but prompt injection can still trigger connected tools and return unbounded tool output into the conversation. Fix: enforce local allowlists from `config["mcp_connections"]`, classify side-effect tools, require confirmation where needed, and truncate/redact outputs.

- **Runtime config fetch fails open to a generic agent.** If `SERVER_API_URL` or `INTERNAL_API_KEY` is missing, [config_handler.py:50](/home/ubuntu/rahul/quickvoice/apps/ai/handlers/config_handler.py:50) falls back to defaults. Production calls can run with the wrong prompt and later fail call logging due missing org/user fields. Fix: fail closed in worker/API runtime except explicit local dev.

## Low Findings

- [livekit_handler.py:53](/home/ubuntu/rahul/quickvoice/apps/ai/handlers/livekit_handler.py:53) does not close `LiveKitAPI` in a `finally` path if egress startup fails after client creation.
- [api.py:56](/home/ubuntu/rahul/quickvoice/apps/ai/api.py:56) says it returns `207 Multi-Status`, but FastAPI returns the default `200`.
- [requirements.txt](/home/ubuntu/rahul/quickvoice/apps/ai/requirements.txt:1) has mostly unpinned dependencies and no lock/constraints or dev requirements, making Python builds hard to reproduce.

## UI/UX Issues

- KB failures and “no KB match” collapse into similar behavior, so users get confident answers or “no matching context” without knowing the KB backend failed.
- The bot can run with the default generic prompt on config misconfiguration, instead of telling callers the agent is unavailable.
- KB processing returns `success: true` even when individual documents fail, which can create confusing status unless every caller inspects per-document results.

## Missing Features

- Fail-closed internal auth middleware for all non-health FastAPI routes.
- Privacy controls in the AI runtime for `store_call_audio`, `zero_pii_retention`, and retention settings; [main.py:277](/home/ubuntu/rahul/quickvoice/apps/ai/main.py:277) always starts recording.
- Durable call-log delivery with retry/backoff and dead-letter visibility.
- Metrics/tracing for provider latency, RAG hit/miss/error, KB document size/chunk count, recording failures, and MCP execution.
- Tests for FastAPI auth, SSRF validation, shutdown finalization, stale vector cleanup, provider failures, and PII redaction.

## New Feature Opportunities

- Add KB citations with document name, page/sheet, chunk ID, and score.
- Add hybrid keyword/vector search and optional reranking for better RAG precision.
- Add per-agent KB ingestion budgets and cost controls.
- Add an admin reindex/delete workflow that removes stale vectors safely.
- Add MCP tool governance: read-only vs write tools, confirmation prompts, and audit summaries.

## Checks Run

- Verified commit with `git rev-parse HEAD`.
- Read `apps/ai` files with line-numbered inspection and scoped ripgrep searches.
- Ran dependency-free syntax check: `syntax ok for 17 python files`.
- Ran `PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -p 'test_*.py' -v`: 3 call-log tests passed; 5 test modules blocked by missing dependencies.
- Reproduced numeric transcript timestamp bug with `build_call_log_payload`.
- Confirmed `apps/ai` remained clean with `git status --short -- apps/ai` and no cache files were created.
- Referenced official LiveKit docs for `ChatContext.created_at` and `JobContext.add_shutdown_callback`: https://docs.livekit.io/reference/python/livekit/agents/

## Blocked Or Unverified

- Full test suite blocked: `pytest`, `loguru`, `livekit`, and `dotenv` are not installed.
- LiveKit, Pinecone, Google embedding, and backend API integrations were not exercised because credentials/services are unavailable.
- No actual SSRF, provider, recording, or MCP network calls were attempted.
- I did not generate security-scan artifacts because the audit rules prohibit creating files.

### packages-config: Shared lint and TypeScript configuration

- Status: `passed`
- Return code: `0`
- Duration: `236.0s`
- Stdout log: `.audit_runs/2026-06-19T062207+0000/packages-config.stdout.jsonl`
- Stderr log: `.audit_runs/2026-06-19T062207+0000/packages-config.stderr.log`

## Summary
Audited only `packages/eslint-config` and `packages/typescript-config` at commit `d0284076a776a80bc39f06d90d56b4c650e366ac`. No repository files were changed. The main risks are weak lint gates, unused shared ESLint config, and TypeScript defaults that leak browser globals into the server.

## Critical/High Findings
- **High: Shared ESLint config downgrades all lint failures to warnings.**
  Proven in [base.js](/home/ubuntu/rahul/quickvoice/packages/eslint-config/base.js:5) and [base.js](/home/ubuntu/rahul/quickvoice/packages/eslint-config/base.js:24): `eslint-plugin-only-warn` is included in the base preset, and `turbo/no-undeclared-env-vars` is explicitly only `warn` at [base.js](/home/ubuntu/rahul/quickvoice/packages/eslint-config/base.js:21). Any consumer of `@repo/eslint-config/base`, `next-js`, or `react-internal` can pass CI with lint violations unless every lint command also uses `--max-warnings=0`.
  **Fix:** remove `only-warn` from shared CI presets, keep it only in a local/dev preset if needed, and make cache-affecting rules such as `turbo/no-undeclared-env-vars` errors in CI.

## Medium Findings
- **Medium: Shared ESLint package is not consumed by the workspace apps.**
  `apps/web` and `apps/console` import `eslint-config-next` directly in [eslint.config.mjs](/home/ubuntu/rahul/quickvoice/apps/web/eslint.config.mjs:2) and [eslint.config.mjs](/home/ubuntu/rahul/quickvoice/apps/console/eslint.config.mjs:2). `rg` found no dependency or import of `@repo/eslint-config` outside its own package.
  **Impact:** changes to `packages/eslint-config` do not protect current app linting.
  **Fix:** either adopt the shared package in app configs or remove/rename it until it is part of the quality gate.

- **Medium: Server inherits DOM typings from the shared TypeScript base.**
  [base.json](/home/ubuntu/rahul/quickvoice/packages/typescript-config/base.json:9) includes `DOM` and `DOM.Iterable`; [apps/server/tsconfig.json](/home/ubuntu/rahul/quickvoice/apps/server/tsconfig.json:3) extends that base.
  **Impact:** server code can typecheck accidental browser APIs such as `window`/`document`, hiding runtime errors.
  **Fix:** make `base.json` runtime-neutral and add separate `node.json` / `browser.json` / `nextjs.json` presets.

- **Medium: Shared Next ESLint preset is weaker than the app presets.**
  [next.js](/home/ubuntu/rahul/quickvoice/packages/eslint-config/next.js:37) wires only `@next/next` rules plus React hooks; it does not use `eslint-config-next/core-web-vitals` or `eslint-config-next/typescript`, unlike the apps. Lockfile data also shows the shared package resolves `@next/eslint-plugin-next@16.2.0` while apps use `eslint-config-next@16.2.1`.
  **Impact:** adopting the shared preset may lose import, accessibility, and exact Next rule coverage.
  **Fix:** base the shared Next preset on `eslint-config-next` or fully mirror its plugin set and versions.

## Low Findings
- **Low: TypeScript config package has no explicit export map.**
  [package.json](/home/ubuntu/rahul/quickvoice/packages/typescript-config/package.json:1) exposes no `exports` for `./base.json`, `./nextjs.json`, or `./react-library.json`.
  **Fix:** add explicit JSON subpath exports to define the supported public surface.

- **Low: Package metadata is inconsistent.**
  `@repo/typescript-config` is `private: true` but also has `publishConfig.access: public` in [package.json](/home/ubuntu/rahul/quickvoice/packages/typescript-config/package.json:4). The ESLint README still says `@turbo/eslint-config` in [README.md](/home/ubuntu/rahul/quickvoice/packages/eslint-config/README.md:1).
  **Fix:** remove stale publish metadata or document publishing intent, and update README naming/usage examples.

## UI/UX Issues
No direct UI surface in this module. Indirect UI risk: the shared Next ESLint preset does not clearly preserve the current apps’ accessibility lint coverage.

## Missing Features
- Package-level smoke tests that import each ESLint export and validate each TS config.
- CI task that runs lint/config validation when `packages/eslint-config/**` changes.
- Runtime-specific TS presets for Node server, Next app, and React library.
- `--max-warnings=0` or equivalent strict lint gate.

## New Feature Opportunities
- Add stricter optional TS presets covering `noUnusedLocals`, `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch`, and `exactOptionalPropertyTypes`.
- Add README examples for each exported config and expected consumer package dependencies.

## Checks Run
- Verified commit with `git rev-parse HEAD`.
- Inspected scoped files, app consumers, root workspace config, lockfile, and CI workflows.
- Searched usages with `rg` for `@repo/eslint-config` and `@repo/typescript-config`.
- Ran `node --check` on all ESLint config JS files: passed.
- Parsed all scoped JSON config/package files with `JSON.parse`: passed.

## Blocked Or Unverified
- `pnpm --filter server check-types`, `pnpm --filter web lint`, and `pnpm --filter console lint` were blocked because `node_modules` is missing (`tsc`/`eslint` not found). I did not run `pnpm install` because it would write repository files.
- ESLint config runtime import checks were also blocked by missing installed dependencies.

## Appendix: Runner Notes

- Each module prompt explicitly instructed Codex not to edit repository files.
- Generated logs are stored under `.audit_runs/` and ignored by git.
- A `changed_files` status means a module session modified tracked files and requires manual inspection before trusting the report.
