Security
Your client’s content in Git. Their secrets never.
Your client’s IT lead asks where their Shopify token lives. You open the project and show them. The content is plain files, with a commit for every save. The token sits beside them, out of Git, and nobody can read it back.
every project’s .gitignore
# Written by Moteur in each project's repository
.moteur/
user-data/
.trash/The project, opened
Where each thing lives
Pick what your client would ask about. Its place lights up, with how it’s kept and who can read it.
client-site/
project.jsonSettings, languages, roles, digests of API keysIn Gitmodels/ pages/ forms/Models, entries, pages, form definitionsIn Git.moteur/secrets.jsonCredentials, encrypted. Readable by the server onlyNever in Git.moteur/Notes, reviews, schedules, the activity logNever in Gituser-data/What visitors send through formsNever in Git
On the server, outside any project
accountsPeople’s sign-in details, kept by the serverNever in GitenvironmentThe server’s own keysNever in Git
- How it’s kept
- Plain files, with a commit for every save. Each one says who changed what, and when.
- Who can read it
- The project’s people, by role. A site sees only what you chose, and only once it’s live.
- How it’s kept
- Encrypted with the server’s own key. It never shows up in the repository’s address.
- Who can read it
- A developer sets it. Nobody can read it back, developers included.
- How it’s kept
- Taken out of project.json each time the project is saved, and encrypted.
- Who can read it
- Content leads and admins set them. The Studio only says one is set.
- How it’s kept
- Written once with the form, kept apart from it. The form only says a secret is set.
- Who can read it
- Builders, with the form. The site only ever sees the public key.
- How it’s kept
- Shown once, when you make it. We keep a fingerprint, not the key, so the commits can’t open anything.
- Who can read it
- Developers make them. Each reads one channel, and only from your client’s domains if you like.
- How it’s kept
- Scrambled one way, so nobody can turn them back into the password. The API never sends them out.
- Who can read it
- Nobody. A forgotten password is reset, never read.
- How it’s kept
- The app’s secret is encrypted. The ten spare codes are kept as fingerprints, each good once.
- Who can read it
- Only the person, with their phone or a spare code.
- How it’s kept
- Kept beside the project, never committed. Spam goes after 30 days. The rest stays until your team deletes it.
- Who can read it
- The project’s people, in the Inbox. Sites and keys can’t read it.
- How it’s kept
- The key that locks the credentials and the one that signs sessions stay with the server, never in a project.
- Who can read it
- Whoever runs the server. Us on Moteur Cloud, you on your own.
Notes, activity and submissions are copied to branches of their own on the server, secrets left out. They never leave with your content.
People
Signing in
Who gets in, and how far they go.
Accounts
Two-step sign-in
Anyone can turn on an authenticator app. Then a stolen password isn’t enough, even after GitHub or Google.
- Ten spare codes for a lost phone
- Sign-in tries are limited
core/src/twoFactor.ts
const TICKET_SECONDS = 5 * 60; const RECOVERY_CODES = 10; /** Ten codes like `K7QX-M2PD`, each good once, for when the phone is lost. */ function newRecoveryCodes(): { shown: string[]; stored: string[] } { const shown = Array.from({ length: RECOVERY_CODES }, () => { const raw = base32Encode(randomBytes(5)).slice(0, 8); return `${raw.slice(0, 4)}-${raw.slice(4)}`; }); return { shown, stored: shown.map(digest) }; }Roles
Each person, their part
The client’s editor writes. Their manager approves. Only your developers change what the site can read.
- Eight roles, set per project
- Nobody sees a project they aren’t in
api/src/middlewares/auth.ts
export function requireDeveloper(req: Request, res: Response, next: NextFunction) { requireProjectAccess(req, res, () => { if (!hasRole(req.user, 'developer')) { return void res.status(403).json({ error: 'Developer access required' }); } next(); }); }Operators
The people who run the server
They sign in on a page of their own, with their own authenticator. A user’s session never works there.
- They can reset a lost phone
- They can’t read anyone’s codes
Sites and services
What reaches in, and what goes out
Keys
A key that only reads
The client’s site gets a key for one channel: the models and fields you chose, once they’re live. Lose it, and you make another.
- Limited to the client’s domains if you like
- Drafts only through a preview link that expires
core/src/projectApiKey.ts
const HASH_ALGORITHM = 'sha256'; function hashKey(rawKey: string): string { return crypto.createHash(HASH_ALGORITHM).update(rawKey, 'utf8').digest('hex'); }Webhooks
Signed, so you know it’s us
Every call Moteur makes to your server carries a signature you can check. It never calls an address inside a private network.
- Tried again if your server is down
- Its secret is encrypted
core/src/webhooks/webhookService.ts
function computeSignature(secret: string, rawBody: string): string { const hmac = crypto.createHmac('sha256', secret); hmac.update(rawBody); return 'sha256=' + hmac.digest('hex'); } // … 'X-Moteur-Event': payload.event, 'X-Moteur-Delivery': delivery.id, 'X-Moteur-Signature': signature, 'X-Moteur-Timestamp': timestampForms
Bots get a thank-you
And they land in Spam. Your emails and entries only happen for people, and the server checks every answer.
- Turnstile, hCaptcha or reCAPTCHA
- Too many sends to one form are refused
core/src/formGuard.ts
export async function screenSubmission( projectId: string, form: FormSchema, proof: SubmissionProof, locale?: string, now = Date.now() ): Promise<Screening> { if (proof.honeypotTriggered && form.honeypot !== false) return { spamReason: 'honeypot' }; const out: Screening = {}; const age = formTokenAge(projectId, form.id, proof.token, now); if (age !== undefined) out.fillSeconds = age; const trap = form.timeTrap; if (age !== undefined && age < (trap?.minSeconds ?? DEFAULT_MIN_SECONDS)) return { ...out, spamReason: 'too_fast' }; // … then the captcha, asked of its provider
On Moteur Cloud, or on your own server1
The engine is the same. What changes is who looks after the machine.
| Moteur Cloud | Your own server | |
|---|---|---|
| The server’s keys | We keep them | You keep them, and back them up. Lose the encryption key, and the credentials are gone |
| Updates | We install them | You install new releases |
| HTTPS and the network | Ours | Yours, in front of the API |
| Backups | Ask us how it’s done | Yours: the repositories, .moteur/ and user-data/ |
| Who can read the content | Your project’s people, by role | The same, and whoever runs the machine |
| Signing in with Google or Microsoft | Talk to us | Turn on the sign-in plugins |
Checked 2026-10-27. Self-hosting covers the engine and its API. The Studio runs on Moteur Cloud.
Questions about security
An agency owner asksWhere does Moteur Cloud keep our data?
MoteurAsk us, and we’ll tell you where your projects run and how they’re backed up. We’ll put the answer here once it’s written down for everyone.
A client’s IT lead asksDo you have SOC 2 or ISO 27001?
MoteurNo. If a client needs it, tell us. Until then, this page and the open-source engine are how you check what we do.
An agency owner asksCan a person ask us to delete what they sent through a form?
MoteurYes. Find their messages in the Inbox and delete them. Or add the form data retention plugin: it finds and erases everything one person sent, and lets each form keep letters only as long as you choose.
An agency owner asksCan an AI agent change a client’s site on its own?
MoteurNot unless the project allows it. An agent works with one person’s permissions, and what it writes waits for a reviewer like anyone else’s.
See where everything lives in your own project.
Start for free, save something, and open the repository.