10 Sep 2026 · Graphai Jobs
Defensive review and fixes on jobs.graphai.eu
The jobs studio was checked with the in-repo Playwright security suite, then the findings that could be fixed in this repo were landed the same day. This is a defensive QA pass, not an offensive penetration test. No login brute force, no fuzzing, no scanner tools, and no exploit payloads.
Related: authorized review of graphai.eu · Linear test plan in the private jobs repo (e2e/TESTPLAN.md).
Scope
- Target:
https://jobs.graphai.eu - When: 10 Sep 2026
- Authorization: Graphai OÜ operator (same repo that serves this host)
- User-Agent:
GraphaiJobsQA/1.0 - Out of scope: Nuclei, sqlmap, SSRF/metadata probes, credential stuffing, rate-limit stress, authenticated studio abuse
Tool: Playwright
Playwright is a browser and HTTP testing library. For this pass we used @playwright/test 1.62.1 from the jobs repo, with playwright.config.mjs pointed at production (JOBS_BASE_URL defaults to https://jobs.graphai.eu).
The security file is e2e/security.spec.js. It uses Playwright’s request fixture (HTTP only, no browser) so checks stay at status codes, redirects, and headers. The suite is labelled “defensive web checks” on purpose: it asserts what a signed-out visitor must not receive. It does not send injection strings or scan for CVEs.
The same repo also has related QA tools that were not required for this pass:
e2e/public.spec.js— landing, sign-in/up, robots, Digital Asset Links- Postman collection
e2e/postman/jobs.graphai.eu.postman_collection.json— same public and auth-gate checks as HTTP requests - testRigor script
e2e/testrigor/jobs.graphai.eu.txt— plain-English clicks plus 401 API checks
Source of truth for the studio is the private GitHub repo that Vercel builds from. Production client errors that users consent to send go to Sentry (EU). Sentry Seer opens GitHub pull requests from those incidents; those stay in the private repo, not on this page.
Command run:
./node_modules/.bin/playwright test e2e/security.spec.js --reporter=list
What we exercised
| Check | What Playwright asserted |
|---|---|
| Landing | HTTPS 200 with an HTML/text content type |
| HSTS | Strict-Transport-Security includes max-age= |
| CORS | Landing Access-Control-Allow-Origin is not * |
| Gallery HTML | Unauthenticated GET redirects (3xx) to sign-up, not the studio |
| Gated HTML | /gallery and /gallery.html are not served as 200 app HTML when signed out |
| Gated APIs | /api/recommended, /docs, /inbox, /probe, /policies, /applications, /progress-report, /report return 401–499 when signed out |
| Open redirect | /sign-up.html?next=https://evil.example/ does not echo that host in the HTML |
| Apply ingest | Anonymous POST /api/apply-spot is not treated as a successful worker ingest |
| Feature request | Empty POST to /api/feature-request is rejected (not a silent 200) |
Result of this run: 17 passed, 0 failed (4 workers). The CORS check was added after reading GitHub issue 3 / Linear GRA-157.
What is already solid on the public host
| Check | Observed 10 Sep 2026 |
|---|---|
| TLS | TLS 1.3, Let’s Encrypt, CN jobs.graphai.eu, valid through 22 Nov 2026 |
| HSTS | max-age=63072000; includeSubDomains |
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
Referrer-Policy | strict-origin-when-cross-origin |
Permissions-Policy | camera and microphone empty; geolocation and passkeys limited to self |
| CSP | frame-ancestors 'self', plus base-uri 'self', object-src 'none', and upgrade-insecure-requests in the follow-up patch |
| CORP | same-origin |
| CORS | Access-Control-Allow-Origin: https://jobs.graphai.eu (not *). Linear GRA-157 is Done. |
| Gallery gate | Signed-out /gallery.html lands on sign-up |
robots.txt | Allows /blog; disallows /api/ and /gallery |
What we changed after the first pass
Same-day patches on the jobs repo (private GitHub jobs-graphai-eu):
- Sign-in
next=only accepts a same-origin path (not an off-site or protocol-relative URL). - Approved blog comments can load without a session; new comments stay pending and are rate-limited.
- Apply CSV import cannot switch the signed-in account’s email.
- Content-Security-Policy now also sets
base-uri,object-src, andupgrade-insecure-requests. /.well-known/security.txtis published (contact hello@graphai.eu).- Sentry Seer pull requests for a Clerk user-button unmount error and a VM screenshot instance-name check were landed and closed.
Local unit tests after those patches: 116 passed. The Playwright file grew to 20 checks (CORS, security.txt, next= helper, public comments GET). Production only matches the new checks after this deploy.
What we did not do
We did not authenticate as a beta user, did not send exploit payloads, and did not scan with Nuclei or sqlmap. A 401 on /api/inbox is proof that this path is gated for anonymous GET — not a proof that every authenticated handler is perfect. Payment webhooks, worker ingest with a valid secret, and Clerk session forgery were out of this pass.
Follow-up
- Keep
e2e/security.spec.jsin the hourly GCP Playwright loop so a regression reopens a GRA bug. - The company landing review remains at graphai.eu web security review.