Security
Security at ReuseForge
This page lists what is implemented, what is not, how to report a problem and how to protect yourself as a buyer. We list the gaps on purpose. A registry that moves money for strangers should be judged on what it admits.
Early draft. This page is our own description of what the code does today. It is an early draft: it has not been reviewed by a lawyer or by an outside security auditor, and it is not a certification.
In plain words
- The website and app are served over HTTPS with a strict content policy and no third-party scripts, apart from Cloudflare's optional analytics script. Passwords are hashed, sessions and API tokens are stored as hashes, and two-factor sign-in is available.
- Payments are simulated and ordering is still being built. The escrow contract has had no independent audit and is not deployed. Do not use real funds.
- Email delivery is not switched on yet, so there are no verification or password-reset emails.
- Found a vulnerability? Report it privately through a GitHub security advisory. We will not take legal action against good-faith research that follows the rules below.
- Listings are written by strangers. Read the licence, check the content hash and run downloaded code in a sandbox.
1. Where we stand
In place today
- HTTPS only, HSTS for one year, strict Content-Security-Policy
- Salted password hashes with a server-side secret; session and token values stored only as hashes
- HttpOnly, Secure, SameSite session cookie; cross-site request check
- TOTP two-factor sign-in with recovery codes; codes cannot be replayed
- Scoped, expiring, revocable API tokens that cannot change credentials
- Rate limits on sign-in, two-factor codes and other abuse-prone endpoints
- A security log you can read, and a way to sign out your other sessions
- Administrator actions are logged, and admin tools need a sign-in from the last 12 hours
- Listing files served with
nosniffand a sandboxing policy - Daily clean-up of expired sessions and one-time tokens
- This disclosure policy and a
security.txt - In the open-source node: signed events and an escrow state machine that ignores forged steps (section 4)
Not done yet
- Independent audit of the escrow contract, and its deployment
- Real-money payment rails. Ordering, escrow and payouts are not switched on; everything is test mode
- Email delivery for verification links, password-reset links and security notices
- Third-party penetration test
- SOC 2, ISO 27001 or any other certification
- Automatic malware or licence scanning of listings
- A sandbox for the
verifycommand - Automatic expiry of old security-log entries
- Application-level encryption of two-factor secrets (they are stored readable so the server can check codes)
- A bug bounty with cash rewards
2. Website and transport
Every page on reuseforge.com carries these headers. Static pages get them from the project's site/_headers file, and pages and API responses produced by the Worker get them from code, kept in sync with that file. API responses carry the transport headers (HSTS, nosniff, Referrer-Policy, Cross-Origin-Opener-Policy and Permissions-Policy); the Content-Security-Policy and X-Frame-Options apply to HTML. An end-to-end test checks the pages against this list.
| Header | Value | What it does |
|---|---|---|
Content-Security-Policy | default-src 'none'; script-src 'self' https://static.cloudflareinsights.com; style-src 'self'; font-src 'self'; img-src 'self' data: https://avatars.githubusercontent.com; connect-src 'self' https://cloudflareinsights.com; frame-ancestors 'none'; object-src 'none'; base-uri 'none'; ... | Only our own scripts, styles and fonts run, plus Cloudflare's analytics script. No inline script or style, no framing, no plugins. The full list is in the response header. |
Strict-Transport-Security | max-age=31536000; includeSubDomains | Browsers use HTTPS for a year. |
X-Content-Type-Options | nosniff | Files are not reinterpreted as another type. |
X-Frame-Options | DENY | Pages cannot be embedded in another site. |
Referrer-Policy | strict-origin-when-cross-origin | Other sites see only our origin. |
Cross-Origin-Opener-Policy | same-origin | Isolates the page from windows that open it. |
Permissions-Policy | camera=(), microphone=(), geolocation=() ... | Turns off browser features we never use. |
All scripts and fonts are served from our own domain. The policy allows a small set of other origins: GitHub's avatar host for images, Cloudflare's analytics script and endpoint, and github.com as the target of the sign-in form. Listing text is escaped when shown. READMEs go through a Markdown renderer that escapes text first and makes only safe link types clickable (http, https, mailto and in-site links), and because of the policy images in READMEs load only from our own domain and GitHub's avatar host.
3. Accounts, sessions and tokens
- Passwords must have at least 10 characters. Common passwords, passwords with very little variety and passwords that contain the first part of your email address are refused. They are hashed with PBKDF2-HMAC-SHA256, 100,000 iterations, a random 16-byte salt per password and a secret pepper that is kept outside the database. That iteration count is the most the Workers runtime allows. It is lower than current OWASP guidance for PBKDF2, which is one reason we also offer two-factor sign-in and GitHub sign-in. Use a long, unique password.
- Sessions. The browser holds a random value in a
__Host-cookie (HttpOnly, Secure, SameSite=Lax). The database stores only its SHA-256 hash, so a database leak does not reveal live sessions. Every sign-in gets a new session id. Sessions last 30 days, an account can hold at most 20 at once, and a sign-in waiting for a two-factor code lasts 10 minutes. Changing your password signs out your other sessions, and a password reset signs out all of them. - Cross-site requests. A request that changes data and carries your cookie must come from our own origin: we check the
Originand Fetch Metadata headers, and the cookie is SameSite=Lax. Otherwise the request is rejected. - Two-factor sign-in uses time-based one-time codes (TOTP, RFC 6238) and ten single-use recovery codes, stored as salted hashes. A code works only once, and a pending sign-in allows five attempts. The authenticator secret has to be readable by the server to check codes, so it is stored in the database as it is.
- Sensitive changes. Changing your email, setting up or turning off two-factor, and deleting your account ask for your password again, if the account has one.
- API tokens (
rf_...) are shown once and stored as SHA-256 hashes. Each has scopes (for exampleread,writeandorders; only administrators can holdadmin), an optional expiry of up to 365 days, and can be revoked. A token cannot change your password, email or two-factor setting, end your sessions or delete your account. Give an agent a token with the least scope it needs. - Abuse limits. Sign-in attempts are limited per account-and-address pair and per address, and guessing spread across many addresses is slowed down. A wrong password and an unknown account get the same error and cost the same time. Password-reset requests get the same answer whether or not the address has an account. The sign-up form does tell you when a handle or email address is already taken.
- Visibility. You can list your sessions with IP address and browser, end any of them, and read your last 50 security events.
- Staff. Nobody becomes an administrator by signing up. Administrator actions are written to the audit log. Admin tools need a sign-in from the last 12 hours (an admin-scoped API token, which only an administrator can create, is exempt).
- Data access. Database queries use bound parameters. Private and unlisted listings and organisation roles are checked on the server, and a listing you may not see answers "not found". Raw file downloads are served with
nosniffand adefault-src 'none'; sandboxpolicy; only plain text, Markdown and JSON are shown inline, and everything else is a download. Avatars must be PNG, JPEG, GIF or WebP and at most 256 KB. - Cleaning up. A daily job deletes expired sessions, spent or expired one-time tokens and old sign-in attempt counters.
4. The open-source ledger and escrow
This section describes the open-source ReuseForge node (the repoblock package). The hosted service does not run it yet: accounts, listings and reviews at reuseforge.com live in the platform's own database, and ordering and escrow are not switched on there.
- Every listing, order, delivery and vote is an event signed with an Ed25519 key and linked to earlier events by SHA-256 hash. Events that fail the signature or hash check are dropped.
- The rules are applied when the log is read: only the buyer can accept or dispute, only an item's seller can deliver it, and only the named escrow agent can confirm a deposit or resolve a dispute. A step signed by the wrong party is ignored the same way on every node.
- The fee rate, arbiter and deadlines are fixed when an order is created, and a refund after a missed delivery deadline needs no one's signature.
- Money is handled in whole cents with fixed rounding, so a payout plan always adds up to the deposit.
The model is explained in Trust and verification and Buying with escrow.
5. What is not guaranteed
Do not use real funds. Payments run in test mode, and ordering, escrow and payouts are still being built in the hosted service. The escrow contract is unaudited reference code. It has not been deployed, and the real-chain rails only build payment links and check deposits. Bitcoin cannot hold a conditional payment, so a Bitcoin escrow would be custodial: you would trust whoever holds the funds.
| You might assume | What is actually true |
|---|---|
| Listings are vetted | They are not reviewed or scanned before they appear. Reports are handled by administrators afterwards. |
| Test pass rates are verified | A pass rate is a signal, not a proof. An attestation says only that a key ran the tests and got a result. Today any identity can attest, and the newest wins. |
| A Verified badge means the code was audited | Our staff set the badge by hand on an account. It says something about the publisher account, not about any listing's code. |
| Email works | Email delivery is not switched on. No verification or password-reset emails are sent, and a forgotten password cannot be reset by email. |
| Deleting something erases it | Deleting a listing hides it and keeps its files and versions in our database. Deleting an account removes personal fields, not everything. The Privacy Policy lists what stays. |
verify is safe to run | It runs a stranger's tests with your user's permissions. It is not a sandbox. Run it in a container or VM with no secrets. |
| A dispute always gets an answer | One arbiter decides. There is no deadline, appeal or jury yet. |
| Keys prove who someone is | Keys are pseudonymous. There is no identity or KYC check. |
| The platform cannot go down or lose data | It is a beta on a single provider. Take your own copy of anything you need. |
6. Report a vulnerability
Use GitHub's private vulnerability reporting. Do not open a public issue for a security problem.
Report privately through a GitHub security advisory on the project repository. Only the maintainers can see it until it is resolved.
The machine-readable policy is at /.well-known/security.txt. We do not publish a PGP key; the advisory is private and encrypted in transit.
What to include
- What you found and where (URL, endpoint, or file and line in the repository).
- Steps to reproduce, ideally a short proof of concept, and what you expected.
- The impact you believe it has, and any account or data you used. Use accounts you created.
- How you would like to be credited, or that you prefer not to be.
What to expect from us
We are a small team, so these are targets we can keep, not guarantees of a large vendor.
| Step | Target |
|---|---|
| We acknowledge your report | 3 business days |
| We tell you whether we can reproduce it and how severe we think it is | 10 business days |
| We fix or mitigate a critical or high issue | 30 days |
| We fix or mitigate other valid issues | 90 days |
| Public disclosure by agreement, after a fix is available | 90 days at most from the report |
If we need longer, we will say why and agree a new date with you. If you hear nothing within the first target, send a short comment on your advisory.
Scope
| In scope | Out of scope |
|---|---|
reuseforge.com and www.reuseforge.com; the API under /api/ and /auth/; the MCP endpoint; the code in the repository, including the contract sources in contracts/ and the signed-ledger logic; listings that execute code or leak data in ways our design says they should not. |
Denial-of-service and volumetric tests; social engineering, phishing or physical attacks on people or providers; vulnerabilities in Cloudflare, GitHub or another third party (report those to them); results from automated scanners with no demonstrated impact; missing headers on pages that handle no data; that payments are simulated; other people's code in listings (report it with the in-app report button instead). |
Rules for testing
- Test only against accounts you own. Do not read, change or delete other users' data. If you reach someone's personal data by accident, stop and tell us.
- Do not degrade the service: keep request rates low and do not run load tests.
- Never use real funds. Everything can be tested in test mode.
- Do not publish details, or give them to others, until we have agreed a date.
Safe harbour
If you act in good faith and follow this policy, we will treat your research as authorised, we will not start or support legal action against you for it, and we will not report you to a platform or authority for it. If a third party starts action, we will say publicly that your work was authorised under this policy. This promise covers our own systems. We cannot waive claims on behalf of Cloudflare, GitHub or anyone else.
Recognition
We cannot pay cash rewards yet. With your permission, we credit you in the advisory and the changelog entry for the fix. We may add a thanks list here.
7. Advice for buyers
Everything on ReuseForge is written by someone else. Treat it as untrusted until you have checked it.
- Read the code first. Look for network calls, install scripts, obfuscated blobs and anything that reads environment variables or files. Agent skills and prompts deserve the same reading: check what they tell an agent it may do.
- Sandbox it. Run downloaded code and the
verifycommand inside a container or throwaway VM that has no secrets, no mounted home folder and, where you can, no outbound network. - Check the content hash. Each version has a content hash, and a seller states a SHA-256 of what they deliver. Compute the hash of what you got and compare it, once orders are live before the acceptance window closes.
- Check the licence. The listing's licence is your right to use the code.
UNKNOWNmeans you have no stated right. Copyleft licences can affect your own project. - Use the window. Once orders are live, the acceptance window is 7 days by default. When it ends in silence, the seller is paid. Dispute before then if something is wrong.
- Prefer signals you can check. A recent version, tests you can run yourself and an active discussion thread count for more than a star count. A Verified badge is set by our staff on an account; it is not a code audit, and a verified publisher can still publish buggy or harmful code.
- Keep payments inside escrow. Once orders are live, if a seller asks you to pay outside the order, report them. We will never ask for your wallet seed phrase or private key.
- Protect your agent. Give an agent an API token with only the scopes it needs, and leave the
ordersscope off any token that does not need to buy. - Report bad listings. Use the report button on the listing; our administrators can remove it or suspend the account. For a flaw in ReuseForge itself, see Report a vulnerability.
8. Related pages
- Trust and verification: what the signed ledger proves and what it does not.
- Buying with escrow: order states, windows, disputes and fees.
- Privacy Policy and Terms of Service.
- SECURITY.md in the repository.
Last updated: