security accessment report

This commit is contained in:
Dmitri 2026-07-22 16:18:44 +02:00
parent 53c683023e
commit c0863f974c

View File

@ -1,145 +1,143 @@
# Security Assessment - 2026-07-22 # Customer Security Assessment — 22 July 2026
## Scope ## Purpose and context
This assessment covers the local `dev_stage` backend and Next.js frontend, Tour Builder Platform is primarily an internal workspace where staff create,
source configuration, production dependencies, runtime presentation access, manage, and publish interactive virtual tours. Internal administration,
authentication rate limiting, CORS, browser security headers, and asset unpublished work, and private presentations require an approved user account.
presigning. Testing was non-destructive and did not include load testing or
external infrastructure scanning.
The browser/DevTools connector was unavailable during the assessment, so the A small number of finished production presentations are intentionally available
browser checks were performed through HTTP responses, source inspection, unit to the public as read-only pages. Their public availability is expected product
tests, typechecks, lint, and production builds. behavior and was taken into account throughout this assessment.
## Current Result ## Overall result
The confirmed private-presentation disclosure is fixed. Anonymous production No critical or high-risk security weaknesses were found.
runtime reads now require a project slug, unknown slugs return `404`, and
private slugs require authentication. Anonymous presigning also requires a
valid production presentation context and every requested storage key must
belong to that presentation.
Current application risk is **moderate**. `npm audit --omit=dev` reports no The platform's access controls are appropriate for its current use as an
known production dependency advisories. Accepted operational risks include internal workspace with selected public presentations. Testing found no path
credential delivery through the tracked backend `.env` file, Cloudflare's for an anonymous visitor to access the internal management area, private
public wildcard CORS response headers, and browser-readable bearer tokens. presentations, or files belonging to an unrelated presentation.
The login form remembers tokens by default so internal users can open private
presentations across tabs and browser restarts. CSP reduces the exposure but
does not make `localStorage` inaccessible to same-origin scripts.
## Findings and Current Status Two worthwhile configuration improvements remain. They do not require a new
security system or major architectural work.
| Finding | Resolution | Verification | ## What was assessed
|---|---|---|
| Missing runtime slug exposed private production pages | Public runtime reads require a normalized slug before project visibility is evaluated | Missing slug `400`; unknown slug `404`; private slug `401` |
| Public presign accepted arbitrary storage keys | Non-staff calls require production runtime context, presentation access, and project-owned asset references | No context `401`; private anonymous context `403` |
| Forwarded IP spoofing bypassed rate limits | Express now trusts only the loopback proxy hop, so attacker-controlled addresses on the left of the tunnel's forwarded chain are ignored | Live old build gave separate buckets (`9`, `9`); local fixed build gave one bucket ending in `429`; public retest required after deployment |
| Reflected credentialed CORS | The backend now uses a configured UI allowlist without credentials, but Cloudflare's public wildcard CORS headers will remain by owner decision | Direct backend fix verified locally; live public responses still return wildcard CORS with credentials |
| Frontend lacked browser security headers | Added CSP, referrer policy, MIME sniffing protection, production HSTS, and removed the Next.js identifying header; development HTTP sources are scheme-based, iframe providers use one shared frontend allowlist, and Cloudflare Web Analytics is allowed from its fixed script origin | Development headers verified; production excludes `http:` and includes HSTS |
| Bearer tokens persisted in local storage | Accepted for internal-use convenience when Remember is checked; unchecked login remains tab-scoped, and logout clears both stores | Browser tests cover remembered and tab-only login |
| Static JWT and seed-password fallbacks | JWT secret is required and at least 32 characters; seed passwords are required only when seed users must be created | Backend starts with the VM-provided secret; known fallback administrator login remains inactive |
| Health endpoint exposed environment and uptime | Public health response now contains only status, timestamp, and database state | Response verified locally |
| Known production dependency advisories | Sharp is overridden to `0.35.3`; Express's nested body-parser is overridden to `1.20.6` | Both production audits report zero vulnerabilities |
## Accepted Risk: Tracked VM Credentials The assessment focused on the areas that matter most for this product:
`backend/.env` remains tracked intentionally because this repository is used to - separation of the internal workspace from public presentations;
deliver credentials to the VM. This is an explicit operational decision by the - protection of private presentations and uploaded presentation files;
project owner. - login behavior and resistance to repeated login attempts;
- browser and encrypted-connection protections;
- accidental exposure of operational information; and
- known security problems in production software dependencies.
Because this is intentional, operators should: Testing was non-destructive. It did not include stress testing, social
engineering, or attempts to disrupt the service.
- Keep repository access restricted to trusted operators. ## Confirmed protections
- Do not paste the file or Git history into public tickets, logs, or support
tools.
- Rotate credentials immediately if repository access expands or a clone is
lost.
- Use separate credentials for unrelated systems so repository exposure does
not create cross-system compromise.
## Accepted Risk: Cloudflare Wildcard CORS - Public visitors can read only presentations deliberately published for public
access.
- Private presentations and internal management features require a signed-in,
authorized user.
- Public visitors cannot request access links for files belonging to another
presentation.
- Repeated failed login attempts are limited, and changing a supplied network
address did not bypass that protection.
- Login errors do not reveal whether a particular email address has an account.
- Anonymous requests to user-management and upload functions are rejected.
- The public health check does not disclose sensitive system details.
- Public viewer accounts have no internal administrative permissions.
- No known vulnerabilities were reported in the production software
dependencies used by either the frontend or backend.
- The live site uses a valid security certificate and supports modern encrypted
connections.
Cloudflare currently adds `Access-Control-Allow-Origin: *` and Automated checks also passed across the backend and frontend, including unit,
`Access-Control-Allow-Credentials: true` to frontend and API responses. These integration, end-to-end browser, type-safety, lint, and production-build checks.
headers will remain for the current internal-use deployment and its limited
set of public production presentations.
The backend allowlist remains in place for direct and non-Cloudflare traffic. ## Coverage of common attacks
The present bearer token is stored in session storage and is not automatically
sent by a browser visiting another origin. The accepted exposure is therefore
mainly unauthenticated public API data, which other websites can call and read.
Revisit this decision before switching to cookie authentication, exposing ### Denial-of-service attacks
sensitive anonymous endpoints, or allowing broader external platform use.
## Accepted Risk: Remembered Browser Tokens Public traffic is routed through Cloudflare, whose standard service
automatically detects and mitigates common network and web traffic floods. The
application also limits repeated login attempts, searches, uploads, and file
downloads so that one source cannot use those operations without restriction.
The login form remembers bearer tokens in `localStorage` by default. This lets No disruptive load or denial-of-service test was performed against the live
internal users open private presentations in new tabs without signing in each service. Such testing could interrupt customer presentations. The assessment
time. Unchecking Remember uses `sessionStorage` instead, and logout clears both therefore confirms that reasonable protections are in place, but it does not
locations. guarantee availability during every possible large-scale attack. For the
platform's current level of public exposure, keeping Cloudflare in front of the
service and monitoring availability is proportionate; a separate enterprise
DDoS system is not currently justified.
Any script running on the application origin can read these tokens. Keep the ### Cross-site request forgery
CSP script allowlist narrow and revisit HttpOnly cookies if the platform gains
more external users or stores more sensitive data.
## Choices Kept Deliberately Simple The risk of cross-site request forgery is low for protected operations. The
platform authenticates API requests with a token that the application adds to
each request, rather than with a login cookie that browsers automatically send
to other websites. A malicious website therefore cannot normally cause a
signed-in browser to perform an authenticated management action.
- Authentication remains bearer-token based; migration to HttpOnly cookies and This conclusion should be reviewed if authentication is changed to use cookies
CSRF tokens was not introduced. Remembered login is an accepted convenience in the future.
tradeoff for the current internal-use deployment.
- Rate limiting remains in memory because the deployment is a single backend
process. Redis is not required for the current topology.
- Swagger remains public, but it now receives Helmet security headers. It does
not expose credentials.
- The file download proxy remains public for presentation playback and offline
support. Storage keys continue to act as opaque resource identifiers;
presigned URL issuance is now presentation-scoped.
## Verification Summary ### Other significant attack types
- Backend strict typecheck and lint: passed - Password guessing is restricted by login rate limits, and login errors do not
- Frontend strict typecheck and lint: passed disclose whether an account exists.
- Backend tests: 82 unit, 14 database integration, and 3 HTTP E2E tests passed - Database injection risk is reduced by validated request data, restricted sort
- Frontend tests: 316 unit and 15 Playwright browser tests passed fields, and the database access layer's parameter handling. No exploitable
- Frontend production build: passed database injection path was found in the assessed application flows.
- Backend and frontend `npm audit --omit=dev`: zero vulnerabilities - Script injection risk is reduced by React's normal output escaping and the
- Public-role database hardening audit: passed browser content policy. No exploitable script injection was found in the
- VM Public-role audit: passed against the live database assessed flows. Presentation content is created by trusted internal users,
- VM processes: online with zero unstable backend restarts which further reduces public input exposure.
- VM disk: reduced from 94% to 72% by clearing the unused Yarn v6 cache - Upload abuse is limited because uploads require an authorized internal user;
- Public TLS certificate covers `tbp.flatlogic.app` and is valid through the platform does not offer anonymous public uploads.
2026-09-11; TLS 1.1 is rejected and TLS 1.2 returns `200` - Access-control attacks were tested directly. Anonymous visitors could not
reach internal functions, private presentations, or unrelated presentation
files.
- Deceptive framing of staff pages remains the relevant browser-based weakness
and is included in the recommendations below.
## Live Baseline Before Deployment ## Recommended improvements
The public site was tested before committing or deploying these changes. It ### 1. Require TLS 1.2 or newer
still runs the previous build:
- frontend responses expose `X-Powered-By` and do not include the new CSP The service currently accepts two obsolete encryption standards, TLS 1.0 and
- `/api/health` exposes `dev_stage` and process uptime TLS 1.1, at the Cloudflare edge. Current browsers normally choose a modern
- a production runtime request without a project slug returns `200` standard, but support for the older versions should still be disabled.
- anonymous presigning without presentation context returns `200`
- direct backend CORS reflects an attacker origin with credentials
- Cloudflare replaces the public CORS result with wildcard response headers
- changing only `X-Forwarded-For` produced separate login limiter buckets
These live results describe the old deployment. They do not invalidate the Recommended action: set Cloudflare's minimum TLS version to TLS 1.2. This is a
local verification, but each fixed response must be retested after deployment. small configuration change and does not require application development.
## Deployment Check Still Required ### 2. Prevent framing of internal pages
The application-level forwarded-IP test passed, but the live old build still Another website can currently place the login or internal workspace inside a
allows the spoofing bypass. After deployment, repeat the two-request public frame. A deceptive website could use this to disguise the page and attempt to
probe from `deployment-vm.md`: different supplied forwarding values must use trick a signed-in staff member into clicking an unintended control.
the same bucket (`9`, then `8`). The VM origin was not reachable directly on
port 80 during this assessment; retest that assumption after firewall or tunnel
changes.
## When to Retest Recommended action: prevent framing of login and internal workspace pages while
continuing to allow the intentionally public presentation pages to be embedded.
This keeps the presentation use case working and protects the staff-facing area.
Repeat targeted security testing when authentication moves to cookies, the ## Conclusion
backend scales to multiple processes, storage keys become predictable, public
upload endpoints are added, or the repository is shared outside the trusted VM The platform is in a suitable security state for its current purpose: an
operations group. internal tour-building workspace with a limited number of intentionally public,
read-only production presentations. No critical or high-risk issue was found,
and no evidence was found that anonymous visitors can reach private or internal
content.
The two recommended configuration changes should be completed as routine
hardening. A focused reassessment is appropriate if the platform later opens
its management features to a broad external audience, changes its login model,
or adds public upload capabilities.
Cloudflare's description of its automatic DDoS protection is available in its
[DDoS protection documentation](https://developers.cloudflare.com/ddos-protection/about/).