In short
-
Report privately through GitHub private vulnerability reporting on
GautamTalksDev/takehome(preferred), or by email to developwith.gt@gmail.com. - We aim to acknowledge within three business days, confirm scope, and fix confirmed issues before public disclosure where possible.
- Good-faith research under this policy is welcome. Test only with your own account, do not access other people's data, and do not disrupt the service.
- Do not open public GitHub issues for exploitable problems.
1. How to report
Preferred: GitHub private vulnerability reporting at https://github.com/GautamTalksDev/takehome/security/advisories/new . This keeps the report private between you and us until a fix is published. (You need a GitHub account.)
Alternative: email developwith.gt@gmail.com with the subject "Security report". Email is not end-to-end encrypted; if your report includes exploit code or anyone's personal data, use GitHub private reporting instead, or email us first and we will agree a secure way to send it.
Please include: what you found, the affected URL, endpoint or file, steps to reproduce, the impact you believe it has, and whether you want to be credited.
2. Scope
In scope
- The website at takehome.gautamkhosla.com (static pages served by a Cloudflare Worker with static assets).
-
The API under
https://takehome.gautamkhosla.com/v1/: signup and email verification, API key authentication, rotation and revocation, account deletion, metering, webhooks (including the address checks that prevent requests to internal networks), and the public listing endpoints. -
The embed widget (
/embed.jsand versioned paths) and the WebAssembly engine served from/engine/. -
The source code in the GitHub repository
GautamTalksDev/takehome, including the engine and the npm and PyPI packages built from it. (The packages are built and tested but not yet published to npm or PyPI.)
Out of scope
- The CRA's Payroll Deductions Online Calculator (report issues to the CRA). Do not test or load it on our behalf.
- Cloudflare, Resend, GitHub or Google systems themselves (use their own programs).
- A customer pointing their own webhook at a third-party host.
- Denial-of-service, load or volume testing.
- Reports about missing best-practice headers or settings without a demonstrated security impact.
- Issues in dependencies with no Takehome-specific impact (report upstream, and tell us if a release needs updating).
- Calculation differences from PDOC that are already listed on the Conformance page. Those are accuracy records, not security defects. Report new calculation errors as ordinary GitHub issues.
3. Rules for research
- Use only accounts and API keys you created. Do not access, change or delete anyone else's account, keys, webhooks or data.
- Do not try to read data from our database (email addresses, key hashes, webhook secrets). If you reach any personal information by accident, stop, do not keep or share it, and tell us in your report.
- Do not run automated scanners at a volume that affects the service, and do not degrade the service for others.
- Do not use social engineering, phishing or physical attacks.
- Do not send verification emails to addresses you do not control.
- Give us a reasonable time to fix the issue before you publish details. We will agree a disclosure date with you.
4. Our commitment
- We aim to acknowledge your report within three business days.
- We will confirm whether it is in scope, ask for missing details if we need them, and keep you informed.
- For confirmed issues, we aim to ship a fix or mitigation on the default branch before public disclosure where possible, and publish a GitHub security advisory.
- We will credit you in the advisory if you want to be credited.
- Safe harbour: we will not pursue legal action against research that is done in good faith and follows this policy. This statement covers only claims we could bring. It cannot authorize testing of Cloudflare, Resend, GitHub, Google or any other party's systems.
There is no paid bug bounty.
5. How Takehome is protected
A short, factual description of the controls in place today:
- HTTPS only. Website pages send HTTP Strict Transport Security for one year, including subdomains, and a strict Content Security Policy that allows scripts, styles and connections only from our own domain and blocks framing by other sites.
- The calculators and the embed widget compute in the browser. Salary figures entered there never reach our servers.
- API keys and email verification tokens are 128-bit random values. We store only their SHA-256 hashes and compare them in constant time. Verification links expire after 24 hours and work once.
- Keyed API routes do not allow cross-origin browser requests.
- Request bodies are capped at 16 KiB (48 KiB for a batch, 8 KiB for signup and verification) and 16 levels of JSON nesting; a batch has at most 25 calculations and a year projection at most 27 pay periods; money amounts must be zero or more, with at most 15 digits before the decimal point and at most 2 after it; every whole number has a stated range; unknown fields are rejected on every calculation route, and so are documented fields the engine does not read, each by name; claim codes 2 to 10 use the CRA's claim-code tables; a year projection refuses, by name, any documented field it cannot apply; request bodies are never stored. A test key may run at most 5,000 calculations a minute per account, and a live key at most 3,600 calls a day (UTC) per account. The logging code refuses to log salary fields and removes keys and webhook secrets from log text.
-
Webhook destinations must use HTTPS, carry no user name or password, be at most 2,048 bytes long (16,384 bytes for all of an account's), not be on
gautamkhosla.com, and pass address checks at registration and before every delivery; redirects are not followed; deliveries are signed with HMAC-SHA256 over the timestamp and body. After registration, an endpoint's address is shown only as its scheme and host, to every key. - Usage metering fails closed: if the meter cannot be read or written, the API returns no calculation.
- Provider credentials are stored as encrypted Cloudflare Worker secrets, not in source code.
Signup is protected by an IP rate limit, Cloudflare Turnstile on the form (checked with Cloudflare after the rate limit and the format checks, before any account or budget is read, and refused if the check cannot be made), a daily budget per network and a resend cooldown per address. It gives the same answer, and runs the same database statements and round trips, whether or not an address has an account: an address that already has keys is emailed a notice instead of a link.
Known limitations we have chosen to document rather than hide: the signup route answers faster when it sends no email (an address inside its resend cooldown), so response time can hint that an address was used recently; someone who controls many separate networks and can pass Turnstile could still use up our email sending quota, after which new signups fail until it resets (each address still gets at most three emails a day; the controls are Turnstile, the per-network budget and an alert at 80 in 24 hours); the alert emails we send ourselves use the same quota, but at most 16 a day from the public service while our database answers, counted in it, and while it cannot answer no request sends any, only our hourly cleanup, once an hour (24 a day at most), so requests without an account or a Turnstile pass cannot use the quota up through them (our closed staging copy, used only for testing, has its own cap of 16 a day); the hosted API runs on Cloudflare's free plan, whose daily request and database limits are shared by every user and with our other services on the same Cloudflare account, so requests from two or more IP addresses, or a small number of accounts each used up to its own daily limits, can make the API unavailable for everyone until 00:00 UTC, and traffic to those other services counts against the same limits (a rate limiting rule at Cloudflare, 10 requests per 10 seconds per IP address, and per-account daily limits bound it; a request refused by any of our limits reads only a few database rows and writes none; the website and its calculators keep working); that rule counts by IP address alone, so test and live calls from one address, and callers behind one shared outbound address, share its 10 requests; webhook signing secrets are stored in readable form because they are needed to sign deliveries; a webhook attempt whose endpoint check ran in the moment before the endpoint was deleted can still arrive a moment after the deletion answered (at most one per endpoint, since an account runs one dispatch or replay at a time, unless our database takes more than 20 seconds to answer that check); and the per-minute rate limits are counted per server instance and are approximate.
Every rule data file is Ed25519-signed except the TD1 claim-code tables, which are pinned by SHA-512 in the engine's source code until they are signed. The private part of the signing key is kept off the repository. The engine trusts only the public key built into it, so a changed or re-signed rule file is refused at load, and a changed claim-code table fails its SHA-512 pin. The first signing key had been published in the repository and was revoked on 22 October 2026; files signed with it are refused.
6. Contact
Gautam Khosla, Ottawa, Ontario, Canada. developwith.gt@gmail.com.