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/:keyhandler (workers/api/src/public-file-access.ts) only serves keys matching the one recognized public shape (freelancer-avatar-<userId>-<uuid>.<ext>); everything else, including anyprivate/-prefixed key, gets the same 404 a missing key would. Hardened as part offix/platform-hardening: this was previously a blocklist (serve anything except a key starting withprivate), which meant a future R2 write landing outside theprivate/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 throughapps/admin-web/worker/index.ts's/api/referenceroute, gated behindrequireAdmin()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¶
mainbranch pushes trigger automatic production deployment (see CI/CD → Deployment) - branch protection onmain(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'sworkflow_dispatchtrigger 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:
- ~~Root
README.mdclaims deployment is manual~~ - fixed:README.mdnow describes the actual automatic-on-push-to-mainpipeline (see CI/CD). ALLOWED_ORIGINSomits 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.- ~~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.
- ~~The two deploy workflows have asymmetric app coverage~~ - fixed for active apps:
deploy-cloudflare.yml's matrix now also includes Admin and Embroidery, matchingci.yml's automatic coverage. Investors intentionally remains asymmetric (manual-only, pre-existing) - see CI/CD → Deployment. - ~~
docs/DEPLOYMENT.mdstates 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").