OWASP Top 10:2025 compliance
lindir treats the OWASP Top 10:2025 as a standing requirement. This document maps each risk to controls, residual risks, and ops guidance.
| ID | Risk | Status | Controls in lindir | Residual risks |
|---|---|---|---|---|
| A01 | Broken Access Control | Partial | Separate Bearer (admin) vs Host (enrolled machine) vs enroll (len_… one-shot, limited to POST /v1/hosts/enroll); HBAC on client login when rules exist; host routes reject missing/invalid enrollment secret; console WS uses short-lived single-use wsTicket (not the admin Bearer in the URL) |
Empty HBAC rule set allows all logins. API tokens are effectively all-admin. Fleet scripts/packages/console are intentional root RCE for any Bearer admin. Admin SPA stores token in sessionStorage (XSS → full control). |
| A02 | Security Misconfiguration | Partial | Bind default 127.0.0.1; refuse weak bootstrapToken unless allowInsecureBootstrap; security response headers (nosniff, X-Frame-Options, Referrer-Policy); TLS via reverse proxy + requireForwardedHttps / trustedProxies |
No Content-Security-Policy. HTTP cleartext by design. LDAP allowAnonymous defaults true. Listening on 0.0.0.0 warns but does not refuse. |
| A03 | Software Supply Chain Failures | Partial | Pin Dub deps via dub.selections.json; pin admin UI via package-lock.json; LDC for release builds; update-client uses distro packages |
Client updates trust the distro package repositories you configure. |
| A04 | Cryptographic Failures | Partial | PBKDF2-HMAC-SHA256 (~210k iter) for passwords; hashed API/enroll tokens; enrollment secrets random; client config/cache mode 600 |
TLS not terminated in-process. Host bundle includes password hashes for offline PAM (host secret compromise → hash dump). Enrollment secrets compared as plaintext strings. |
| A05 | Injection | Strong | Parameterized SQL (?); package name allowlist; typed JSON fields; maxBodyBytes; energy drop-ins from allowlisted enums/ints |
Fleet queries/scripts and MDM custom compliance scripts execute shell on hosts by design (admin-trusted). |
| A06 | Insecure Design | Partial | Optional VPN (default null); least privilege host vs admin APIs; password hashes only on Host-auth channel; offline PAM refuses fallback on 401/403 | HBAC empty-set allow is easy to leave open. No approval workflow for fleet RCE / energy / logind changes. |
| A07 | Authentication Failures | Strong | Bootstrap vs API tokens + revoke; rate limiting; account policy (min length, expiry, lockout, must-change); WebAuthn for OIDC; generic client auth errors; enroll tokens (TTL / maxUses / hostname hint) | Admin API is long-lived Bearer without MFA/step-up. Compromised admin token = full domain. |
| A08 | Software or Data Integrity Failures | Partial | Versioned SQL migrations (001–012…); audit log of admin mutations; sudoers written with restrictive mode |
Client bundle / inventory / energy payloads are auth-protected but not signed. MITM without TLS can rewrite host policy. |
| A09 | Security Logging and Alerting Failures | Partial | audit_events for auth ok/deny, tokens, fleet (incl. package results with hostname), enroll, account policy, VPN, MDM; searchable Admin Audit (newest-first sort, expandable package-job stdout/stderr) + CLI filters/CSV export; Dashboard denial strip |
Alerts (webhooks, SIEM, paging) for denial spikes, enroll abuse, or failed package jobs are left to your surrounding stack. |
| A10 | Mishandling of Exceptional Conditions | Partial | Generic internal when exposeErrorDetails is false; rate limit → 429; offline PAM fail-closed on 401/403 |
Empty HBAC fails open. Some Exception catches ignore (e.g. early account_policy ensure). Energy/logind restart failure only warns. |
Config knobs (server)
| Key | Default | Purpose |
|---|---|---|
bootstrapToken |
(required strong) | Initial admin secret — refuse known-weak values unless allowInsecureBootstrap |
allowInsecureBootstrap |
false |
Permit known-weak bootstrap tokens (labs only) |
rateLimitPerMinute |
120 |
Per-source IP request budget |
maxBodyBytes |
262144 |
Max HTTP body size (fleet job output / inventory) |
exposeErrorDetails |
false |
Include exception text in 500 responses |
requireForwardedHttps |
false |
Require X-Forwarded-Proto: https (use behind TLS reverse proxy) |
trustedProxies |
[] |
Optional; when non-empty, only trust forwarded headers from these client IPs |
Ops: TLS
The HTTP server speaks cleartext. For production, terminate TLS on Caddy/nginx/Traefik on the Tailscale or LAN interface, set requireForwardedHttps: true, and do not expose the backend port publicly.
Client host hardening
/etc/lindir/client.json, cache, andcache-auth.json: mode600- Use online PAM auth when possible; offline fallback only on unreachable/transient errors (not on 401/403)
- Offline cache TTL (
offlineMaxAgeSeconds); password hashes kept out of the main policy cache - Run
lindir-client syncas root via systemd timer; validate sudoers withvisudo -cf - Energy / package / script jobs require root sync — treat Bearer admins as highly privileged
Verification
- Unit tests:
dub test - Manual: attempt API without token → 401; host API without secret → 401; oversized body → 413; weak bootstrap with defaults → refuse to start; enroll token cannot call non-enroll admin routes → 403
