Platform security
Recrut'Auto applies a defense in depth strategy across 5 independent layers, from the perimeter firewall to application-level validation. This page summarizes the protections your integrations (REST API, MCP, LinkedIn plugin) go through and the guarantees you can expect.
Layer 1 — Edge & IP intelligence
At the edge of our infrastructure, we filter known malicious IPs before any application processing:
- CrowdSec Community Blocklist (~15,000 auto-updated IPs) — Shodan/Censys scanners, SSH brute-force bots, IPs flagged by the open-source CrowdSec community across all connected Security Engines.
- Behavioral detection —
http-bf,http-crawl-non_statics,http-probing,http-bad-user-agentscenarios continuously analyze logs and automatically ban IPs triggering attack patterns. - Kernel-level drop at the perimeter firewall: banned IPs are rejected before even reaching the application load balancer. Reaction latency: ~10 seconds between detection and effective ban.
Layer 2 — TLS & rate-limiting
All external communications use TLS 1.2+ with automatically renewed Let's Encrypt certificates. Internal certificates are issued via the DNS-01 challenge, without exposing any internal service on the Internet.
For rate-limiting, you are capped by default at:
| Surface | Limit |
|---|---|
REST API /api/* (Bearer token) | 60 r/min per user |
| Admin surface | 20 r/s |
| Public endpoints (booking) | 10 r/s |
| MCP (Bearer PAT) | 30 r/s |
| Sentry tunnel | 30 r/s |
Beyond that, you receive a 429 Too Many Requests. Implement an exponential backoff.
Layer 3 — Auth & ownership
All /api/* endpoints (except explicit public opt-ins like /api/calendars/public/*) require one of the following authentication modes:
- Keycloak Bearer JWT (user logged in on the front end, OIDC PKCE).
- Personal Access Token (
Authorization: Bearer ra_<token>, see Tokens). - Scoped HS256-signed token (public booking 14 days, candidate image 10 minutes).
Once authenticated, each request is filtered by subscription ownership: you only see your subscription's resources, never those of another tenant. This rule is enforced at each backend service (get_subscription(db, current_user)), not only at the database level.
Sensitive non-user endpoints (LinkedIn reply analysis by our ML engine, etc.) are protected by rotatable static internal tokens, never exposed to the browser or the plugin.
Layer 4 — Application validation
- Body cap — requests are rejected with
413 Payload Too Largebeyond:- 10 MiB on the common
/api/*trunk - 4 KiB on feature flags
- 50 MiB on CV import
- 10 MiB on the common
- Transfer-Encoding sanity — requests with a malformed
Transfer-Encodingheader are rejected with400(request-smuggling mitigation). - Pydantic v2 strict mode on critical schemas (
extra=forbid+max_length+patternregex on IDs/slugs). A payload with an unexpected field is rejected with422. - MIME validation on uploads (CV PDF, interview audio recordings) — verified via
python-magic, not via the client-declared content-type. - ClamAV antivirus scan on all recruiter uploads (
POST /api/import/cv,POST /api/import/csv,POST /api/interview/meets/{id}/recording). Fail-closed policy: if the clamd daemon is unreachable, the upload is rejected with503 antivirus_unavailablerather than risking an unscanned file. A detected threat returns422; the ClamAV signature name is logged internally but never returned to the client to prevent an attacker from iterating to find a bypass. Admins have a/securitypage to test the daemon (status card, test-file scan, EICAR button).
Layer 5 — Private data & assets
Candidate profile images (GET /api/candidates/{id}/image) are served via a short-lived signed URL mechanism:
- Your application calls
GET /api/candidates/{id}/image-urlwith a Bearer token. - The backend validates ownership and returns an audience-scoped HS256 JWT, valid for 10 minutes.
- The
<img src=...?token=JWT>tag is consumed by the browser without re-authentication.
This authenticates the asset without exposing the Bearer token in an <img> request. Interview recordings (S3) follow the same pattern with presigned URLs.
Audit logging
All security events (auth gate hit, body cap exceeded, invalid signature, IP whitelist match) are emitted on the structured logger recrutauto.security and collected in Loki. Anomalies (an abnormal anonymous_warning rate, for example) are alerted via Sentry.
Common error codes
| Code | Meaning |
|---|---|
400 | Malformed request (suspicious Transfer-Encoding, invalid JSON) |
401 | Token missing, expired, or invalid |
402 | No active subscription — subscribe to a plan |
403 | Insufficient permissions or IP banned at the perimeter |
404 | Resource not found or outside your subscription's scope |
413 | Body too large |
422 | Pydantic validation failed (unknown field, invalid type, length exceeded) |
429 | Rate limit exceeded |
Integration best practices
- Store Personal Access Tokens in a vault (HashiCorp Vault, AWS Secrets Manager, etc.), never in clear text in code.
- Rotation: generate a new token every 90 days and revoke the old one after switching.
- Scope minimally: if your integration only reads campaigns, avoid using an admin token.
- Back off quickly on 429 — a polite client avoids being mistaken for a scraper and banned at the perimeter.
- Verify the TLS certificate on the client side. No Recrut'Auto endpoint should be called over unencrypted HTTP.
Report a vulnerability
If you discover a security flaw, contact security@recrutauto.fr (PGP key available on request). We commit to acknowledging receipt within 24 business hours and to coordinating a responsible disclosure.
This page describes the behavior observable from your integrations. The full internal architecture (components, ops runbooks) is documented separately for RecrutAuto teams.