Security Review
backendApi development @ a6445ea09 · capExpertApp development @ d9293b779 · mobile checked · 19 Oct 2026
Verdict: critical. The review found server-side code execution, ways for users to reach other organisations' accounts and data, and internal endpoints that accept requests they should refuse.
12 of the worst findings are fixed (11 fully, 1 partly). The fixes are committed on a branch but not yet deployed, so all of them are still live in production. This public page leaves out file locations, endpoints and reproduction steps on purpose; the full report is kept off the web.
On this page
- What happens next
- backendApi: critical and high
- backendApi: medium and low
- capExpertApp
- Mobile
- Scope and method
What happens next
- Review, merge and deploy the CAP-246 commits (one per repo, already on the branch). Before merging, check adding and editing users (as an admin and as a Primary user), 2FA login, opening chat, and a shipping-quote reply email.
- Rotate the token-signing secret after that deploy, so tokens issued before the fix stop working.
- Rotate or restrict the Google API key found in git history (B-M16).
- List the service accounts that run background jobs (web API, Cloud Scheduler, mobile API). Needed for B-C4.
- Lock the internal task endpoints in two deploys (B-C3): first make every caller send a signed token, including the mobile API, then enforce it.
- Design the replacement for signed file links (B-C7) around record ids, since several features legitimately open other accounts' files. Then work through the open highs.
backendApi: critical and high
| ID | Severity | Area | Finding | Status |
|---|---|---|---|---|
| B-C1 | Critical | CoPilot | Server-side code execution from request input | Fixed in branch |
| B-C2 | Critical | User management | Editing a user can move it into, or take over, accounts in other organisations | Fixed in branch |
| B-C3 | Critical | Background tasks | Internal task endpoints accept unauthenticated requests | Needs input |
| B-C4 | Critical | Background jobs | The job runner accepts tokens from any Google service account | Needs input |
| B-C5 | Critical | Login | Two-factor login and logout can be bypassed | Fixed in branch |
| B-C6 | Critical | Shipping emails | A token sent in shipping emails works as a login | Fixed in branch |
| B-C7 | Critical | File storage | Signed file links can be issued for other organisations' documents | Open |
| B-H1 | High | Content admin | Server-side code execution from admin-only request input | Fixed in branch |
| B-H2 | High | Equipment planning | SQL injection in an admin bulk update | Open |
| B-H3 | High | User management | Users can be created in accounts the caller does not manage | Fixed in branch |
| B-H4 | High | Notifications | An unauthenticated stream exposed every organisation's notifications | Fixed in branch |
| B-H5 | High | Login | The import service accepted half-finished 2FA logins | Fixed in branch |
| B-H6 | High | Login | Password change works without a session and without an attempt limit | Open |
| B-H7 | High | Chat | Chat history readable outside your own rooms | Partly fixed |
| B-H8 | High | Capture | Room import into another organisation's project | Open |
| B-H9 | High | Document Manager | Documents can point at other organisations' files | Open |
| B-H10 | High | Assistant | Assistant attachments can point at other organisations' files | Open |
| B-H11 | High | Admin roles | Limited admins can reset any user's password | Open |
| B-H12 | High | Onboarding | Registration links issued for other organisations' pending users | Open |
| B-H13 | High | CoPilot | A vendor can decline an RFP on another vendor's behalf | Open |
| B-H14 | High | CoPilot | Bids and prices readable across organisations | Open |
| B-H15 | High | Dashboard | Any user can change dashboard layouts platform-wide | Open |
backendApi: medium and low
| ID | Area | Finding | Status |
|---|---|---|---|
| B-M1 | Admin roles | Limited admins can act outside their assigned accounts | Open |
| B-M2 | Notifications | Other users' notifications can be read or changed | Open |
| B-M3 | Vendor offers | A vendor can cancel another vendor's offer | Open |
| B-M4 | Denovo / CoPilot | Some pre-order and bid details readable across organisations | Open |
| B-M5 | Accounts | Primary-user contact details readable for any account | Open |
| B-M6 | Permissions | Feature permissions open up when the cache is down | Open |
| B-M7 | Sessions | Suspending a user or changing a password does not end sessions | Open |
| B-M8 | Login | No rate limits; login codes have no attempt limit; captcha is the client's choice | Open |
| B-M9 | Account settings | The 'require 2FA' setting is not enforced | Open |
| B-M10 | Printing | The print-signing service works without login | Open |
| B-M11 | Uploads | Upload links are not limited by account, size or type | Open |
| B-M12 | PDF generation | PDF rendering runs untrusted content without a sandbox | Open |
| B-M13 | Email ingestion | Inbound email fetches remote content and trusts attachments and senders | Open |
| B-M14 | Assistant analytics | The database-analysis guard can be bypassed (feature off by default) | Open |
| B-M15 | Tokens | Tokens are no longer written to logs; Denovo link expiry and the Dialogflow agent credential are still open | Partly fixed |
| B-M16 | Secrets | A Google API key remains in git history and must be rotated; the ignore files now block example env files | Open |
| B-M17 | Dependencies | Vulnerable packages that the code actually reaches | Open |
| B-M18 | Exports | Export files briefly downloadable without login | Open |
| B-M19 | Search | A long search can exhaust server CPU | Open |
| B-M20 | Imports | Archive uploads are not size-limited | Open |
| B-M21 | Stickers | Unvalidated input forwarded to an external service | Open |
| B-M22 | Action feeds | Service dates can be changed on other organisations' equipment | Open |
| B-M23 | Asset registry | Verify works on other organisations' assets | Open |
| B-M24 | Import logs | Import logs readable for other accounts | Open |
| B-M25 | Documents | The document log accepts other accounts and file locations | Open |
13 low findings are hardening items: stricter origin matching, user-enumeration responses, webhook signature checks, guard structure, error logging, a public default admin inbox, and dead code that should be deleted. All open.
capExpertApp
| ID | Severity | Area | Finding | Status |
|---|---|---|---|---|
| F-C1 | Critical | Shared rich text | Stored cross-site scripting in bids, listings, comments and logs | Fixed in branch |
| F-H1 | High | Account settings | Org-wide 'require 2FA' is only a banner (backend fix, B-M9) | Open |
| F-H2 | High | Admin screens | Cross-site scripting reaching platform-admin sessions | Fixed in branch |
| F-M1 | Medium | Login | Open redirect after login | Fixed in branch |
| F-M2 | Medium | Hardening | Browser security policy allows inline scripts; tokens kept in local storage | Open |
| F-M3 | Medium | Chat | Session token sent in a URL | Open |
| F-M4 | Medium | Login | Captcha requirement decided by the client | Open |
| F-M5 | Medium | SSO | The SSO callback can switch a signed-in user into another account | Open |
| F-M6 | Medium | Denovo external links | A non-expiring link token stays in the page URL | Open |
9 low findings cover token handling in URLs and storage, logout clean-up, editor configuration, and dependency bumps. None of the front-end dependency advisories is reachable in the browser. All open.
Mobile
- The mobile app talks only to the mobile API, so none of the branch fixes changes anything for mobile users.
- The mobile API calls the web API server-side for background work, so it is part of the two open items about background tasks and jobs (B-C3, B-C4).
Scope and method
- Read-only static review: nine auditors across two rounds, plus a front-end and API cross-check and a mobile check. Nothing was tested against a live service.
- Every critical finding and the riskiest highs were re-read by hand against the source.
- Not covered: two sub-admin controllers, about three quarters of raw-SQL sites (triaged by search), and anything only visible in cloud configuration.
- Status key: Fixed in branch committed on CAP-246, not deployed · Partly fixed · Needs input waiting on information from the team · Open