---
title: Mistral Outsource Package — Audit Remediation 2026-06-10
tags: [outsource, audit, remediation, mistral]
created: 2026-06-10
status: ready-to-send
---

# Mistral Outsource Package — Audit Remediation 2026-06-10

Self-contained task package for an external AI agent (Mistral) working against the Amanat escrow codebase.

**Repo root:** `/Users/manwe/CascadeProjects/escrow`  
**Active branch for frontend/backend:** `feature/white-label-shops`  
**Active branch for scanner:** `development`  
**Active branch for deployment:** `main`

Each task is independent. Complete them in any order. Do not touch files outside the listed scope. Do not print secret values from `.env` files — reference only by variable name.

---

## Task 1 — M5: Scanner must fail startup when SCANNER_API_KEY is missing in production

**File:** `scanner/config.go`

**Context:** Lines 128–131 print a warning when `SCANNER_API_KEY` is empty but let the process start anyway. In production this means the scanner exposes all endpoints unauthenticated.

**What to do:**

Add an environment-gated hard-fail. If `SCANNER_API_KEY` is empty **and** `APP_ENV` is `production` (or `SCANNER_REQUIRE_AUTH=true`), call `log.Fatal(...)` / `os.Exit(1)` instead of `slog.Warn(...)`.

Keep existing dev-mode behaviour: if `APP_ENV` is not `production` and `SCANNER_REQUIRE_AUTH` is not `true`, keep the warn-only path.

Example shape (adapt to actual Go idioms used in the file):

```go
if cfg.APIKey == "" {
    if os.Getenv("APP_ENV") == "production" || os.Getenv("SCANNER_REQUIRE_AUTH") == "true" {
        log.Fatal("[scanner] SCANNER_API_KEY must be set in production — refusing to start unauthenticated")
    }
    slog.Warn("[scanner] SCANNER_API_KEY is not set — all endpoints are unauthenticated (dev mode only)")
}
```

**Verification:** `go build ./...` and `go test ./...` must still pass.

---

## Task 2 — M4: Telegram debug panel must not show in production without explicit admin/developer role

**File:** `frontend/src/components/debug/telegram-debug-panel.tsx`

**Context:** Lines 44–56 set `showPanel = true` whenever `NODE_ENV !== 'production'` OR the page is opened inside a Telegram Mini App context OR `debug=1` / `amn-debug=1` is present in the URL/localStorage. This means the panel is always visible inside the Mini App in production, exposing user email, wallet address, internal API/socket URLs, and Telegram platform/version to any user.

**What to do:**

Extend the `showPanel` logic so that in production it only activates when **both** the debug request is present **and** the authenticated user has role `admin` or `developer`.

The component already has access to `user` from `useAuthContext()`:

```tsx
const { user, authenticated, loading } = useAuthContext();
```

Replace the `setShowPanel(...)` call inside the `useEffect` with:

```tsx
const isPrivileged = user?.role === 'admin' || user?.role === 'developer';
setShowPanel(
  process.env.NODE_ENV !== 'production' ||
  (nextContext.isMiniApp && isPrivileged && debugRequested) ||
  (debugRequested && isPrivileged)
);
```

Remove the bare `nextContext.isMiniApp` condition that shows the panel to all Mini App users.

Also update the initial `useState` default so it reads from user context properly — or just default to `false` and let the effect set it (safe since the effect runs on mount).

**Verification:** TypeScript compile (`npx tsc --noEmit`) must pass for this file.

---

## Task 3 — L1a: Remove hardcoded `password123` from deployment docker-compose files

**Files:**
- `deployment/docker-compose.yml` line 152: `MONGO_INITDB_ROOT_PASSWORD=password123`
- `deployment/dev-amn/docker-compose.yml` line 101: `MONGO_INITDB_ROOT_PASSWORD=password123`

**What to do:**

Replace each hardcoded `password123` with an env-var reference:

```yaml
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD:-changeme_local}
```

Use `changeme_local` as the fallback, not `password123`, so it is obvious this is a placeholder that must be replaced in real deploys.

Add a comment above each block:

```yaml
# Set MONGO_INITDB_ROOT_PASSWORD in your .env — default is local-only placeholder
```

**Verification:** `docker compose config` must not error (dry-run parse only — do not start containers).

---

## Task 3b — L1b: Fix `undefined` password literals in migration SQL

**File:** `deployment/escrow-multi/migrate/migrations/0018_db_privilege_isolation.sql`

**Context:** Lines 11 and 19 create Postgres roles with `PASSWORD 'undefined'` — this is a literal string `undefined`, not a variable substitution. These were almost certainly written by accident.

**What to do:**

Replace the two `PASSWORD 'undefined'` literals:

```sql
-- Before
CREATE ROLE escrow_vital_user WITH LOGIN PASSWORD 'undefined';
CREATE ROLE escrow_nonvital_user WITH LOGIN PASSWORD 'undefined';

-- After
CREATE ROLE escrow_vital_user WITH LOGIN PASSWORD :'escrow_vital_password';
CREATE ROLE escrow_nonvital_user WITH LOGIN PASSWORD :'escrow_nonvital_password';
```

If psql variable syntax is not appropriate for the migration runner in use, use a clearly wrong placeholder value that can never accidentally work:

```sql
CREATE ROLE escrow_vital_user WITH LOGIN PASSWORD 'REPLACE_ME_escrow_vital';
CREATE ROLE escrow_nonvital_user WITH LOGIN PASSWORD 'REPLACE_ME_escrow_nonvital';
```

Add a comment: `-- TODO: inject real passwords via migration runner env — do not commit real credentials`.

**Verification:** SQL must parse (`psql --dry-run` or equivalent syntax check).

---

## Task 4 — M6 Backend: Fix ESLint errors in backend (auto-fix pass + manual cleanup)

**Directory:** `backend/`

**Context:** `npm run lint` reports 29 errors including forbidden `require()` imports, empty catch blocks, and TypeScript namespace usage. 996 warnings exist but are lower priority.

**What to do:**

1. Run `cd backend && npm run lint -- --fix` to auto-fix all auto-fixable issues.

2. Manually fix remaining errors in these categories:
   - **Forbidden `require()` imports**: Replace `const x = require('y')` with `import x from 'y'` (or `import * as x from 'y'` for namespace imports). Do not change the runtime behaviour.
   - **Empty catch blocks**: Add a minimal comment `// intentional` or add `_err` as the parameter and log it if it looks like it should be logged. Do not silently swallow errors that would hide real bugs.
   - **TypeScript namespace usage**: If a `namespace Foo {}` can be a plain `module` or `interface`/`type` grouping, convert it. If the namespace is part of a declaration file or ambient module, keep it.

3. After manual fixes, run `npm run lint` again and confirm error count is 0 (warnings are acceptable).

4. Run `npm run typecheck` to ensure no regressions.

**Verification:** `npm run lint` exits 0 errors. `npm run typecheck` passes.

---

## Task 5 — M1: Remove `ignoreBuildErrors` from frontend Next.js config and fix resulting TS errors

**File:** `frontend/next.config.ts`

**Context:** Line 29 sets `typescript: { ignoreBuildErrors: true }`, masking type errors that reach production builds. The pre-push `tsc` hook is supposed to catch these, but production builds currently silently swallow them.

**What to do:**

Remove the `ignoreBuildErrors: true` line (or change to `ignoreBuildErrors: false`). Update the comment to reflect this:

```ts
// TypeScript errors are caught here (Next.js build) and by the tsc-guard pre-push hook.
typescript: { ignoreBuildErrors: false },
```

Then run `npx yarn lint` and `npx tsc --noEmit -p tsconfig.json` inside `frontend/`. Fix any type errors that surface. Common patterns expected:

- Components with `@ts-nocheck` at the top — remove the suppression and fix the underlying type.
- `any` casts that can be narrowed.
- Missing `key` props on lists.

**Do not** fix type errors in payment or wallet components without reading the code carefully. If a type error in those files requires understanding complex payment domain logic, leave a `// TODO(audit): type error — needs domain review` comment and move on.

**Verification:** `npx tsc --noEmit` exits 0. `npx yarn build` completes without TypeScript errors.

---

## Task 6 — L2: Extend magic-byte validation to generic file upload routes

**Files:**
- `backend/src/services/file/fileController.ts` — generic upload handler
- `backend/src/services/file/chatAttachmentController.ts` (or similar) — reference: this file already has magic-byte validation

**Context:** The chat attachment upload path validates file magic bytes (file signatures) to ensure the actual content matches the declared MIME type. Generic uploads (product images, request templates, blog images) rely only on the MIME type declared by the client, which can be spoofed.

**What to do:**

1. Find the magic-byte validation function in the chat attachment controller. It likely reads the first N bytes of the upload buffer and compares against known signatures.

2. Extract or re-use that function in a shared utility: `backend/src/services/file/fileMagicBytes.ts` (or add it to `fileService.ts` if that's the right home).

3. Call the magic-byte check in `fileController.ts` for **all** upload routes that accept user-controlled files. Reject with HTTP 415 if the magic bytes do not match the declared MIME type.

4. Do not change the existing chat attachment path — it already works correctly.

**Verification:** `npm run typecheck` passes. Add or update a test in `backend/__tests__/` that uploads a file with a mismatched MIME/magic-byte pair and asserts HTTP 415.

---

## Notes for the executing agent

- **Never print** the contents of `.env`, `.env.dev`, or any variable containing `KEY`, `TOKEN`, `SECRET`, `PASSWORD`, or `PRIVATE`.
- All changes must be on the branches specified at the top of this document.
- Frontend changes: `feature/white-label-shops`. Backend changes: `feature/white-label-shops`. Scanner: `development`. Deployment: `main`.
- Do not bump `package.json` version numbers — the orchestrating agent handles version bumps before any deploy.
- Do not modify Woodpecker pipeline files, Dockerfiles, or CI configuration.
- Each task's verification command must pass before marking the task done.
