Skip to content

Security

Purpose

The security-relevant facts about this repository's infrastructure, gathered from direct inspection - not a generic checklist. Includes findings that are worth a deliberate decision rather than a silent fix.

Secrets and credentials

See CI/CD → Secrets for the full GitHub-secret-vs-Worker-secret breakdown, the CLOUDFLARE_API_TOKEN rotation procedure, and least-privilege scoping.

Environment separation (or lack of it)

There is no staging tier - every credential capable of wrangler deploy/--remote is a production credential. See Environments. This raises the bar on how carefully CLOUDFLARE_API_TOKEN access is granted: anyone who can trigger deploy-cloudflare.yml or land a commit on main can affect production.

D1 and R2 access

  • D1: no row-level access control - the API Worker's own route handlers are the only authorization boundary. See Cloudflare → D1.
  • R2: object access is gated by an application-level allowlist, not a bucket ACL - the central API Worker's /api/files/:key handler (workers/api/src/public-file-access.ts) only serves keys matching the one recognized public shape (freelancer-avatar-<userId>-<uuid>.<ext>); everything else, including any private/-prefixed key, gets the same 404 a missing key would. Hardened as part of fix/platform-hardening: this was previously a blocklist (serve anything except a key starting with private), which meant a future R2 write landing outside the private/ prefix - by a bug, a copy-paste, or a new feature - would have been silently exposed with no authentication at all. The genuinely private object category (embroidery reference images, private/embroidery-references/...) was already, independently, only retrievable through apps/admin-web/worker/index.ts's /api/reference route, gated behind requireAdmin() session auth - that path is unchanged. See Cloudflare → R2 for the full detail.

CORS

workers/api/wrangler.jsonc's vars.ALLOWED_ORIGINS allowlists calling origins for the central API Worker: Corporate, Software, Logistics, Commerce, Embroidery. Admin does not call this Worker at all (it has its own separate Worker/D1 access at admin.akshayagroupglobal.com) and correctly has no entry. Investors (investors.akshayagroupglobal.com) also has no entry - verified: apps/investors-web currently makes zero fetch calls to any API, including a backend POST /api/investors/enquiries route that already exists in workers/api/src/index.ts but is not yet wired up from the Investors Contact page. Per an explicit platform-scope decision, Investors is currently excluded / inactive for the active platform - ALLOWED_ORIGINS intentionally does not include it, and this documentation effort does not add it. Revisit if/when Investors re-enters active scope (at which point the existing backend endpoint would need front-end wiring too, not just a CORS entry).

Deployment permissions

  • main branch pushes trigger automatic production deployment (see CI/CD → Deployment) - branch protection on main (required reviews, status checks) is the practical control point for "who can cause a production deploy," since the workflow itself has no additional approval gate.
  • deploy-cloudflare.yml's workflow_dispatch trigger is available to anyone with write access who can run workflows in this repository - it is not restricted to a smaller group via GitHub Environments' required-reviewers feature (not configured here).

Secret rotation

See CI/CD → Secrets "Rotating CLOUDFLARE_API_TOKEN" - sourced from docs/DEPLOYMENT.md §6.

Findings from this portal's initial review, and their resolution

Per this portal's own ground rules (document actual infrastructure, do not silently alter it), the initial review recorded findings rather than changes. fix/platform-hardening (a separate, deliberate follow-up PR) resolved the ones with a clear, minimal, non-production-affecting fix, and left the ones requiring a real product/scope decision as an explicit, documented choice:

  1. ~~Root README.md claims deployment is manual~~ - fixed: README.md now describes the actual automatic-on-push-to-main pipeline (see CI/CD).
  2. ALLOWED_ORIGINS omits the Investors domain - not a defect: Investors is currently excluded / inactive for the active platform by explicit scope decision (see "CORS" above); no code change made.
  3. ~~R2 access control was an application-level blocklist string check~~ - fixed: hardened to an explicit allowlist (see "D1 and R2 access" above); the actually-private object category was independently already authenticated elsewhere and needed no change.
  4. ~~The two deploy workflows have asymmetric app coverage~~ - fixed for active apps: deploy-cloudflare.yml's matrix now also includes Admin and Embroidery, matching ci.yml's automatic coverage. Investors intentionally remains asymmetric (manual-only, pre-existing) - see CI/CD → Deployment.
  5. ~~docs/DEPLOYMENT.md states migrations are numbered "0001-0005"~~ - fixed: corrected to describe running every ordered file in the migrations directory rather than a hardcoded range, so the instruction stays valid as migrations increase (see Architecture → Data Platform).

Before committing anything to this repository

Always grep a diff for password|secret|token|api_key|account_id|private_key before staging - this portal's own contribution followed that same check before its own commit (see CI/CD → Secrets "Security considerations").