Single-page React/TypeScript marketing site for Creators.ae. Email capture posts directly to monday.com from the browser — with a live me:write API token shipped in the JS bundle.
.env is committed to git and, because of the REACT_APP_ prefix, the token is also inlined into the production JS bundle. Anyone can extract it.@tailwind directives reference a Tailwind install that does not exist; the default CRA test is broken; the splash screen is a hard-coded 5.5 s delay.master via the Bitbucket web editor. No CI, no branch protection, no review trail.| Category | Technology | Version | Notes |
|---|---|---|---|
| Framework | Create React App (react-scripts) | ^5.0.1 | No Vite/Next; REACT_APP_* envs are inlined into bundle. |
| Language | TypeScript | ^4.9.5 | strict: true, target ES5, jsx react-jsx. |
| UI library | React + React DOM | ^18.3.1 | Concurrent root via createRoot. |
| Styling | CSS Modules + global CSS | — | Per-component *.module.css; global in src/index.css. |
| Tailwind | (referenced but not installed) | — | @tailwind directives in index.css — no package, no config. Silently dropped. |
| Fonts | @fontsource/poppins, custom Greycliff (woff) | ^5.0.14 | Greycliff files live in public/font/. |
| Routing | — | — | None. Single page; scroll-to-section via refs. |
| Data | monday.com GraphQL API | v2 | Single mutation: create_item on a hard-coded board. |
| Testing | Jest + React Testing Library | CRA defaults | One test, broken (default placeholder). |
| Lint/format | eslint-config-react-app only | — | No Prettier, no extra rules, no CI. |
Single-page Create React App. index.tsx mounts <App/> which renders <Page/>. Page owns three refs (home/about/notified) which it passes into the navbar so each nav link can scrollIntoView the corresponding section. The email form section calls monday.com's GraphQL endpoint directly from the browser using the token shipped in the bundle. There is no server, no router, no DB.
This assessment was conducted as a white-box static source review of the full repository, git history, and the locked dependency tree, grounded in the anthropic-cybersecurity-skills playbooks (which encode OWASP WSTG, OWASP API Top 10, and MASVS methodology). Findings were produced by parallel per-dimension scanners — client-side secrets, XSS/client injection, client auth/token handling, supply chain/CI-CD, and misconfiguration/headers — then deduplicated and adversarially verified against the actual code, decoded JWTs, and the package-lock.json. Every finding below is rated with CWE, CVSS 3.1, and OWASP Top 10 2021 mappings.
implementing-secret-scanning-with-gitleaks, testing-for-sensitive-data-exposure, performing-cryptographic-audit-of-applicationtesting-for-xss-vulnerabilities, exploiting-prototype-pollution-in-javascript, testing-for-open-redirect-vulnerabilitiestesting-jwt-token-security, testing-api-authentication-weaknesses, testing-oauth2-implementation-flawsperforming-sca-dependency-scanning-with-snyk, analyzing-sbom-for-supply-chain-vulnerabilities, detecting-supply-chain-attacks-in-ci-cd, prioritizing-vulnerabilities-with-cvss-scoringtesting-cors-misconfiguration, testing-for-host-header-injection
This single-page Create React App marketing site for creators.ae ships a live monday.com personal API JWT (per:"me:write", account 5465124) two ways at once: it is committed in a git-tracked .env and, because of the CRA REACT_APP_ prefix, it is also string-substituted into the production JS bundle and sent verbatim as the Authorization header from every visitor's browser to api.monday.com. Any visitor or anyone who can read the repo obtains a full account-write credential, and git history retains a second, previously-rotated me:write token (uid 50058541) that a "rotation" never revoked — so deleting the file does nothing. The same email form builds its GraphQL create_item mutation by string-concatenating the user-supplied email into the query before a permissive (quote-allowing) regex even runs, giving a client-side GraphQL injection that, combined with the public token, lets an attacker run arbitrary token-authorized mutations (delete_item, change_column_value, reads) directly against the workspace with no rate limit, CAPTCHA, or server boundary. There is no CI/CD, branch protection, or secret scanning, and changes are committed straight to master via the Bitbucket web editor of an external personal org (ParhamandCo), so the path from arbitrary edit to production has no integrity gate. The build toolchain is end-of-life (react-scripts 5.0.1) and freezes a transitive tree with multiple published CVEs, though those are predominantly build/dev-time rather than reachable against the static production artifact.
| Category | Status | Note |
|---|---|---|
| A01:2021 Broken Access Control | N/A | No backend, no auth, no server-side access control surface in this static SPA; the form posts to monday.com via a client-held token (access-control failure manifests as the leaked credential, tracked under A02). |
| A02:2021 Cryptographic Failures | Vulnerable | Live monday.com me:write JWT committed in .env, inlined into the public bundle, and retained in git history (CW-01, CW-02). |
| A03:2021 Injection | Vulnerable | GraphQL injection: user email string-concatenated into the create_item mutation before client-only validation (CW-03). |
| A04:2021 Insecure Design | Partial | Architecturally insecure: a write-capable token in the browser with no server boundary, no rate-limit/CAPTCHA/CSRF on the only form; the proper design is a server-side proxy (remediation in CW-01/CW-03). |
| A05:2021 Security Misconfiguration | Vulnerable | Default-on production source maps (CW-06) and no CSP/security headers (CW-09); also the .gitignore that fails to exclude .env (CW-01). |
| A06:2021 Vulnerable and Outdated Components | Vulnerable | EOL CRA 5 toolchain (CW-04), nth-check ReDoS CVE-2021-3803 (CW-07), broad build/dev CVE cluster (CW-08), and no SCA/update automation (CW-10). |
| A07:2021 Identification and Authentication Failures | N/A | No user authentication system in the repo (no login, sessions, or password handling). |
| A08:2021 Software and Data Integrity Failures | Vulnerable | No CI/CD, no branch protection, direct-to-master web-editor commits on an external personal org, no npm ci/lockfile enforcement (CW-05). |
| A09:2021 Security Logging and Monitoring Failures | Partial | No logging/monitoring of form abuse or token misuse; client-side only console.error. Low relevance for a static marketing site but worth noting once a proxy exists. |
| A10:2021 Server-Side Request Forgery (SSRF) | N/A | No server-side request construction from user input; the single outbound fetch target (api.monday.com) is hard-coded. |
The repo commits a real monday.com personal API token (HS256 JWT) in a git-tracked .env that .gitignore does not exclude. The decoded payload confirms a live, non-expiring per:"me:write" token scoped to monday.com account actid=5465124 (user uid=12149628). Because this is Create React App, every REACT_APP_* variable is statically substituted into the compiled JS at build time, so the token is (a) readable by anyone who clones/browses the repo and (b) shipped to and readable by every site visitor via View-Source/DevTools, where it is sent verbatim as the Authorization header to api.monday.com. A me:write token carries the full write capability of the issuing user across the account — enumerate/modify/delete board items, read items (including PII captured by the form), pull account users, and abuse API quota. There is no expiry claim, so it stays valid until manually revoked. This is one canonical issue consolidated from three scanner reports (client_secrets x2 and supply_chain_fe).
/static/js/*.js), greps for 'eyJ' to recover the JWT, then calls https://api.monday.com/v2 directly with Authorization: <token> to dump board 6870444819 (all collected subscriber emails) and/or delete/alter items — entirely out-of-band from the form, with no rate limit or auth barrier.Full compromise of the monday.com account's write surface for user 12149628: an attacker who extracts the token (trivially, from the public bundle or git) can read every email address captured by the form, create/modify/delete board items, enumerate workspace data, and burn the account's API quota. With no backend proxy, every page load redistributes the credential; combined with the unauthenticated, no-CAPTCHA/no-rate-limit form (CW-05) the leaked write token is a ready-made data-tampering and data-exfiltration channel. The email PII collected via the form is therefore exposed to any token holder — a GDPR-relevant data-protection gap for an .ae marketing audience.
git rm --cached .env and add .env to .gitignore. 4) Scope the replacement token to least privilege (a dedicated integration/service user limited to board 6870444819) rather than a personal me:write token. 5) Add a pre-commit + CI secret scanner (gitleaks/trufflehog).Commit 0bd04c0 ("new api key") rotated the token only in the working tree; the previous token remains in commit e4ad776. That old JWT decodes to a distinct me:write token (uid=50058541) on the same account (actid=5465124), issued 2024-06-20 with no expiry. Anyone with clone access to the Bitbucket repo can recover both tokens by walking history. A rotation that does not also revoke the old token at the provider and scrub history leaves the old credential live; there is no in-repo evidence the old token was revoked at monday.com. Even the current token's deletion from the working tree would not restore confidentiality because it persists in 0bd04c0.
git show e4ad776:.env, recovers the 2024-06-20 me:write JWT, and — if it was never revoked at monday.com — uses it to read/modify account 5465124 long after the 'rotation' supposedly closed access.If the old token (uid 50058541) was not revoked at monday.com it is a second live me:write credential to the same account, recoverable from history by anyone with clone access. Standard 'remove the file' remediation will NOT help; the secrets must be revoked at the provider and history rewritten. This raises the integrity/availability ceiling for any board the account can touch.
git-filter-repo/BFG to purge .env from all commits, then force-push with team coordination. 3) Enroll the repo in gitleaks pre-commit + CI so .env/JWT patterns are blocked going forward. 4) Document a rotation runbook: revoke at provider FIRST, then scrub, never the reverse.The email-capture form builds its monday.com GraphQL mutation by directly interpolating the user-controlled email into the query string (item_name: "${email}") with no escaping. The query is assembled (line 31) before the validation regex runs (line 37), so validation does not gate construction; moreover the regex only rejects whitespace and '@' and explicitly permits the double-quote, so even a 'valid' value like a"@b.co passes. An attacker types a " to break out of the item_name literal and append arbitrary GraphQL. Because GraphQL accepts multiple top-level fields, an injected payload can chain change_column_value, delete_item, or create_item on other boards (anything the token is authorized for) or alias query fields to read accessible data. Validation is also purely client-side (CW-03 absorbs the standalone 'client-side-only validation' weakness), so an attacker can equally edit the POST body in DevTools/Burp. Scored S:C (changed scope) because the injection executes against a separate system, monday.com, under the bundled token's authority.
a") { id } delete_item(item_id: <known_id>) { id } create_item(board_id: 6870444819, item_name:"x in the email field (or edits the request body in Burp since validation is client-only). The browser posts the concatenated mutation with the bundled token; monday.com executes both create_item and the injected delete_item. Iterating over enumerated item IDs corrupts or wipes the board; the same payloads also fire directly at api.monday.com using the leaked token, bypassing the form entirely.Arbitrary GraphQL operations executed against the client's monday.com workspace within the inlined token's privileges: tampering with or deleting board items, modifying column values, and reading data the token can access. With the token also public (CW-01), there is no authentication barrier — any visitor or bot can issue crafted mutations at scale with no rate limit, CAPTCHA, or CSRF control. Board data integrity impact is high; confidentiality is bounded by token scope.
{ query: 'mutation($boardId: ID!, $name: String!){ create_item(board_id:$boardId, item_name:$name){ id } }', variables: { boardId, name: email } }. Run validateEmail() and reject BEFORE constructing or sending anything, and tighten the regex to disallow quotes/control chars (or use a strict validator). Critically, move submission behind a server-side proxy so neither token nor raw query is built in the browser; have the proxy validate, parameterize, and rate-limit/CAPTCHA. Treat client-side validation as UX only.The entire build pipeline depends on react-scripts@5.0.1 (CRA), which is deprecated and unreleased since 2022, so the build tooling and its pinned transitive graph receive no security fixes. CRA was officially deprecated by the React team (Feb 2025 docs no longer recommend it); react-scripts 5.0.1 (Apr 2022) is the last release. CRA cannot be partially upgraded without eject/migration; the frozen tree is the root cause of the individual CVE clusters (CW-07, CW-08). There is no Vite/Next migration and no SCA automation.
The project is structurally stuck on EOL build tooling: new CVEs in webpack/svgo/babel/rollup cannot be remediated by simple bumps and each requires overrides or a full migration. Over time the build environment accumulates unpatched vulnerabilities. Practical attack surface is build-time/dev (dev server, source-map/HTML generation), not the anonymous-internet production artifact — hence A-impact via build/CI disruption rather than runtime C/I.
package.json overrides to force patched transitive versions (see CW-07/CW-08), enable npm audit/Snyk in CI, and add Renovate/Dependabot for ongoing updates.There is no CI/CD of any kind: no automated build verification, no dependency/secret scanning gate, no npm ci integrity install, no SAST/SCA before deploy. The code lives on a Bitbucket repo under an external personal org (ParhamandCo) and history shows direct commits to master including edits through the Bitbucket web editor, meaning anyone with web access to that account can push production changes (including the api-key change) with no review, branch protection, or pipeline checks. This is a software-integrity/governance failure: the path from arbitrary edit to production has no integrity controls.
notified-form.tsx (or adds a dependency) to exfiltrate every submitted email to an attacker domain, commits straight to master, and the change deploys to creators.ae with no review, test, or scan to stop it.A compromised or over-privileged Bitbucket account (or a malicious insider) can push arbitrary code straight to the deployed marketing site without review — malicious dependencies, exfiltration scripts, defacement. Absence of npm ci/lockfile enforcement and SCA means a dependency-confusion or compromised-package update could deploy silently. External-org ownership compounds key-rotation and offboarding risk.
npm ci, npm audit --audit-level=high, secret scanning (gitleaks), build, and tests on every PR. Enable branch protection on master (require PR + review, disable direct/web-editor pushes), enforce the lockfile, and move repo ownership into an A-List-controlled namespace with least-privilege access. Pin third-party CI actions to commit SHAs once a pipeline exists.CRA emits production source maps by default. With GENERATE_SOURCEMAP unset and a bare react-scripts build, the deployed /static/js/*.js.map files will contain the original TypeScript source, making the already-inlined token (CW-01) and the injectable GraphQL query (CW-03) trivially readable and exposing the full client source structure. This is a secondary amplifier of the token exposure rather than an independent secret, and is conditional on the deployment actually serving the .map files (most static hosts do), hence 'likely' / medium.
/static/js/main.<hash>.js.map, reconstructs notified-form.tsx with original variable names, reads the inlined REACT_APP_API_KEY and the exact create_item mutation shape, then crafts a precise injection/replay payload.Lowers the effort to extract the inlined credential and to understand the injectable GraphQL request shape; aids targeted abuse of the leaked token and of the form injection. No independent impact beyond CW-01/CW-03.
GENERATE_SOURCEMAP=false for production builds (or strip/deny-list *.map at the CDN/host). This is hardening only; the real fix is removing the token from the client entirely (CW-01).Rated "likely" — the source-map emission is confirmed by the bare build script and CRA defaults, but exploitation depends on the (unverified) static host actually serving the .map files. Most do.
nth-check < 2.0.1 contains an inefficient regular expression (ReDoS) parsing nth-child expressions, pulled transitively through svgo@1.3.2 via @svgr/webpack (the CRA SVG-as-component loader). This is the well-known high-severity finding npm audit reports on every default CRA 5 project. It is build-time/dev-only reachable here (SVGs are processed at build), not at runtime in the browser, which constrains real-world impact for this static site.
npm run build, svgo's css-select invokes vulnerable nth-check on it, pinning a CPU core and hanging the build/CI indefinitely.A crafted SVG processed by the build could cause a CPU-bound hang of the build/dev process (DoS of the build pipeline). Not exploitable against site visitors at runtime.
npm overrides entry forcing nth-check>=2.0.1 (and css-select>=4) across the tree, or migrate off CRA/svgo 1.x (CW-04). Verify with npm audit post-override. Build only with trusted SVG assets.The CRA 5 transitive tree pins numerous packages to versions with published CVEs. Notables: postcss 7.0.39 under resolve-url-loader (CVE-2023-44270 CSS line-parsing/injection), webpack-dev-server 4.15.2 (CVE-2025-30359/30360 — a malicious site visited while the dev server runs can read source via non-allowlisted origins), rollup 2.79.1 (CVE-2024-47068 DOM Clobbering that can inject script into generated bundles), and the express/connect dev-server stack (path-to-regexp ReDoS, send/serve-static path handling, body-parser DoS, cookie name parsing). Most are build-time/local-dev only for a static SPA and are not reachable by anonymous internet users against the deployed artifact (hence P2), though rollup CVE-2024-47068 can theoretically affect generated output and webpack-dev-server source-leak affects any developer running npm start.
npm start and, in another tab, visits an attacker page that abuses the webpack-dev-server origin weakness (CVE-2025-30359/30360) to read the project's source over the dev-server socket, exfiltrating proprietary code and the inlined token from the dev bundle.Primary risk is to developers/build infrastructure: source-code disclosure when running the dev server alongside a malicious site, ReDoS/DoS of build or dev processes, and (rollup) a small chance of DOM-clobbering-injected script in produced bundles. No direct anonymous-internet impact on the static production site beyond the rollup output concern.
npm overrides: postcss>=8.4.31, rollup>=2.79.2 (or 3.29.5/4.22.4), path-to-regexp>=0.1.10, send>=0.19.0, serve-static>=1.16.0, body-parser>=1.20.3, cookie>=0.7.0, nanoid>=3.3.8, cross-spawn>=7.0.6, micromatch>=4.0.8, http-proxy-middleware>=2.0.9, webpack-dev-server>=5.2.1 (or never browse untrusted sites while npm start runs). Long term, migrate off CRA (CW-04). Run npm audit in CI.The app ships no CSP (neither a meta tag in index.html nor any server header config in the repo) and no framing/MIME hardening headers. There is currently no DOM-XSS sink in the source (React JSX auto-escapes; no dangerouslySetInnerHTML/innerHTML/eval/document.write), so this is a missing defense-in-depth layer rather than an active vulnerability. A restrictive CSP would contain any future inline-script or third-party-script compromise and is standard for a public marketing site.
If a future code change or compromised dependency/CDN introduces an injection sink, there is no CSP to contain script execution, no frame-ancestors protection against clickjacking of the email form, and no Referrer-Policy to limit leakage. No exploitable impact today.
default-src 'self'; script-src 'self'; connect-src 'self' https://api.monday.com; object-src 'none'; base-uri 'none'; frame-ancestors 'none'). Add X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, X-Frame-Options: DENY. Once the form is proxied (CW-01), drop api.monday.com from connect-src.There is no continuous dependency monitoring (no Snyk/Dependabot/Renovate) and no npm audit gate, consistent with there being no CI. The lockfile itself is healthy (v3, subresource-integrity hashes, registry-only resolution, only expected install scripts), so there is no evidence of an active implant. But without npm ci enforced anywhere and without automated advisory tracking, newly disclosed CVEs in the 1530-package tree go unnoticed and an npm install could drift from the lockfile. Two declared devDependencies (ajv, dotenv) are unused, adding needless surface.
npm install (rather than npm ci) silently pulls a drifted or malicious patch version into a build that no pipeline scans.Slow detection of newly disclosed dependency vulnerabilities and risk of lockfile drift during installs. Low immediate exploitability; a hygiene/governance gap that amplifies the EOL-toolchain risk (CW-04).
npm ci && npm audit --audit-level=high (or Snyk) step in CI. Enforce npm ci for all installs/builds to honor the lockfile. Remove unused devDependencies (ajv, dotenv). Use --ignore-scripts where feasible and review lifecycle scripts on dependency bumps.e4ad776) is also revoked.me:write token.git rm --cached .env and add .env to .gitignore.validateEmail() (tightened to reject quotes/control chars) BEFORE constructing or sending anything.git-filter-repo/BFG to purge .env from all commits, then force-push with team coordination.npm ci, npm audit --audit-level=high, secret scanning (gitleaks), build, and tests on every PR; enforce the lockfile and pin CI actions to commit SHAs.package.json overrides to force patched transitive versions: nth-check>=2.0.1, postcss>=8.4.31, rollup>=2.79.2, path-to-regexp>=0.1.10, send>=0.19.0, serve-static>=1.16.0, body-parser>=1.20.3, cookie>=0.7.0, nanoid>=3.3.8, cross-spawn>=7.0.6, micromatch>=4.0.8, http-proxy-middleware>=2.0.9, webpack-dev-server>=5.2.1.GENERATE_SOURCEMAP=false for production builds (or deny-list *.map at the CDN/host).default-src 'self'; script-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none') plus X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, X-Frame-Options: DENY.page.tsx at ~105 lines). No complexity hotspots — the whole app fits in your head.strict: true is on, but env vars are force-cast with as string rather than narrowed; page.tsx uses non-null assertions implicitly via HTMLElement cast in index.tsx.<NavBar /> in home.tsx:6; duplicate setMobileMenuOpen logic between nav-bar.tsx and navigation-bar.tsx is fine but flat; setupTests.ts is the default stub.type="text" instead of type="email"; no <label> for the email field (placeholder-only); nav "links" are <div>s with onClick rather than <button>s or anchors; the burger menu has no aria-expanded / aria-label.git clone <bitbucket-url>
cd creators-website
npm install
# .env is already in the repo (P0 — see findings). Rotate the key before
# doing anything else, then move it out of git.
npm start # http://localhost:3000
npm run build # writes /build (gitignored)
npm test # currently fails — broken placeholder test
REACT_APP_API_KEY — monday.com personal API token (JWT). Inlined into the production JS bundle by CRA.REACT_APP_BOARD_ID — monday.com board where email subscribers are created as items (currently 6870444819)..nvmrc; CRA 5 requires Node 14+. Node 18 LTS recommended.No CI config, no Dockerfile, no infra-as-code in the repo. The Bitbucket remote is ParhamandCo/creators-website. The output of npm run build is a static build/ directory that can be served by any static host (Cloudflare Pages, Netlify, S3+CloudFront, Vercel static). Whatever the current host is, the bundled monday.com token is being served alongside the HTML — fixing that needs a small serverless function in front of the form post.
index.php or .htaccess — there isn't one..env is the single biggest issue. Anything you push without fixing this just buries the problem deeper in history. Rotate first, then code.REACT_APP_* is a CRA convention that means "expose this to the browser". Treat any value you put there as fully public. There is no way to keep a secret here without a server-side hop.poppins, font-greycliff, gradient-text, font-greycliff-demibold etc. are hand-written rules in src/index.css (or font-package globals), not Tailwind utilities. Search index.css before assuming anything.page.tsx, pass it through NavigationBar → NavBar, and attach it to the wrapper div.master takes direct pushes. Be deliberate. There is no review or CI safety net today.react-scripts internals — if a bigger refactor is on the table, migrate to Vite or Next.js.