Security Review
Round 2 · backendApi, capExpertApp and dbMigrations · 19 Oct 2026
Verdict: critical. The round-1 fixes that landed hold up. Internal job endpoints are still open, and they now turn out to reach the database directly. Round 2 also found several ways for admin-side and vendor-side users to act beyond their role.
This public page leaves out file locations, endpoints, parameters and reproduction steps on purpose. The full report is kept off the web.
4 critical open1 new · 3 from round 1
+1 fixed in branch, awaiting dev
+1 fixed in branch, awaiting dev
15 high open5 new · 10 from round 1 (2 partly fixed)
+1 fixed in branch, awaiting dev
+1 fixed in branch, awaiting dev
53 medium open25 new · 28 from round 1 (4 partly fixed)
+2 not re-checked · 1 fixed in branch
+2 not re-checked · 1 fixed in branch
14+ low open14 new · most round-1 lows still open
On this page
What happens next
- This week: lock the internal job endpoints. Stop keeping credentials in the email log. Scope the admin-side password and role tools. Pin vendor identity on offers. Add the approver check to budget approval. Remove the leftover public file serving and the production API docs. Fix the front-end security headers.
- This sprint: make exported files and signed agreements private. Close the remaining cross-organisation reads. Add attempt limits and session revocation. Add row locks on checkout and offer acceptance. Put expiry on emailed links.
- This quarter: one shared tenant-scope guard, strict request validation everywhere, and safe SQL helpers. Rate limiting, least-privilege database and cloud identities, and encryption of stored credentials. Hardened container images and an audit log.
- Process: static analysis and secret scanning in CI, dependency gating, per-role authorization tests, and a security checklist in pull requests.
- Rotate the token-signing secret and the keys found in git history after the token fixes ship.
Critical and high
IDs starting with R2- are new this round; the others are from round 1.
| ID | Severity | Area | Finding | Status |
|---|---|---|---|---|
| B-C3 | Critical | Background tasks | Internal task endpoints accept unauthenticated requests | Open, worse |
| R2-SQL-1 | Critical | Background jobs | Internal job payloads reach database queries without validation | Open |
| B-C4 | Critical | Background jobs | Job runner accepts any Google service account | Open |
| B-C7 | Critical | File storage | Signed links for other organisations' documents | Open |
| F-C1, F-H2 | Critical | Front end | Stored script injection in rich text | Fixed in branch |
| B-H11 | High | Admin user management | Partial admins can reset passwords | Open, worse |
| B-H14 | High | CoPilot | Bids and prices readable across organisations | Open, worse |
| R2-AUTHZ-1 | High | Admin user management | Some admin roles can change passwords and roles of users above them | Open |
| R2-DATA-1 | High | Email log | Credential emails are stored and readable by admins | Open |
| R2-STOR-1 | High | File storage | Exported files and signed agreements are stored publicly readable | Open |
| R2-BIZ-1 | High | Marketplace offers | A vendor can make offers in another vendor's name | Open |
| R2-BIZ-2 | High | CaptAIn / budgets | Assistant actions can approve budgets without approver rights | Open |
| B-H2 | High | Equipment planning | SQL injection in an admin bulk update | Open |
| B-H6 | High | Login | Password change without a session or attempt limit | Open |
| B-H8 | High | Capture | Room import into any project | Open |
| B-H10 | High | Chatbot | Chat file reference reaches any stored file | Open |
| B-H15 | High | Dashboard | Layouts changeable platform-wide | Open |
| F-H1 | High | Login | Organisation "require 2FA" not enforced | Open |
| B-H7 | High | Chat | Chat rooms readable without membership | Partly fixed |
| B-H12 | High | Onboarding | Registration links handed out too widely | Partly fixed |
| B-H9 | High | Document Manager | Any stored file path accepted | Fixed in branch |
Medium and low
IDs starting with R2- are new this round; the others are from round 1.
| ID | Severity | Area | Finding | Status |
|---|---|---|---|---|
| R2-SQL-2 | Medium | Equipment planning | SQL injection in an admin-only report filter | Open |
| R2-AUTHZ-2 | Medium | Marketplace | Vendors can read other organisations' equipment components | Open |
| R2-AUTHZ-3 | Medium | Transactions | Platform margin can be changed without admin rights | Open |
| R2-BIZ-3 | Medium | Checkout and offers | Double submission can create duplicate orders and offers | Open |
| R2-BIZ-4 | Medium | Marketplace | Sellers can set their own listing valuation | Open |
| R2-BIZ-5 | Medium | Shipping quotes | Shipping price can change after the buyer approved it | Open |
| R2-BIZ-6 | Medium | Document extraction | AI extraction can be pointed at other organisations' files | Open |
| R2-AI-1 | Medium | CoPilot AI summary | Bid text can steer the AI summary sent to hospitals | Open |
| R2-DOS-1 | Medium | API-wide | List sizes, query time and request rates are not limited | Open |
| R2-DOS-2 | Medium | AI features | No per-user limit on AI usage | Open |
| R2-DB-1 | Medium | Database | The application uses the database owner account | Open |
| R2-DB-2 | Medium | Database / CaptAIn | The analytics role's tenant boundary is incomplete | Open |
| R2-DB-3 | Medium | Database | Login links and third-party credentials are stored unencrypted | Open |
| R2-INJ-1 | Medium | User text is inserted into emails as HTML | Open | |
| R2-PHI-1 | Medium | Equipment photos | Photos flagged as containing patient information are not access-controlled | Open |
| R2-LOG-1 | Medium | Audit | Admin actions and data reads are not audit-logged | Open |
| R2-AUTH-1 | Medium | Login | Email lookups allow pattern searching of accounts | Open |
| R2-AUTH-2 | Medium | CoPilot chat email | Reply links in chat emails never expire | Open |
| R2-FE-1 | Medium | Front end | Rich-text cleaning still allows fake forms and page overlays | Open |
| R2-FE-2 | Medium | Front end | Security headers are missing on the main page | Open |
| R2-INFRA-1 | Medium | Cloud | A long-lived cloud key file is used at runtime | Open |
| R2-INFRA-2 | Medium | Containers | Images are outdated and run as root | Open |
| R2-INFRA-3 | Medium | API docs | The API documentation password can be bypassed | Open |
| R2-INFRA-4 | Medium | Dependencies | Backend package lockfile lacks integrity hashes | Open |
| B-M1–B-M25 | Medium | Backend, various | Of 25: 0 fixed, 4 partly fixed, 19 open (4 confirmed on the local stack, 3 worse than reported), 2 not re-checked | Mostly open |
| F-M2–F-M6 | Medium | Front end | Content policy, token handling, captcha, SSO, external links | Open |
| F-M1 | Medium | Login | Open redirect after login | Fixed in branch |
| R2-L1–L14 | Low | Various | 14 hardening items: query escaping, retention, stored PINs and 2FA secrets, offer workflow edges, guard composition, third-party document viewers | Open |
| Lows | Low | Both repos | Nearly all still open; one closed by stricter input handling | Mostly open |
Scope and method
- Read-only review of backendApi (both roles), capExpertApp and dbMigrations, by seven parallel reviewers. Each critical and the main highs were re-checked by hand.
- Some findings were confirmed on a local copy of the stack, without changing data or sending messages. Nothing was tested against production.
- Every database query site in the backend was classified this round, about 2,700 in all.
- Not covered: mobile apps, extraction services, and cloud settings that can't be seen from code (IAM, ingress, bucket permissions, key restrictions).
- Status key: Fixed in branch committed, waiting for the dev deploy · Partly fixed · Open