GitHub
05/13/2026, 1:36 PMosctrl-api / osctrl-tls. Bundled as one PR because the pieces share a coherent threat model (anonymous attack surfaces, cross-tenant exposure, credential-bearing payloads). No API contract changes for existing clients — JSON shapes, route paths, and request bodies are unchanged.
End-to-end tested against a local Kali docker deployment.
Themes
Auth bedrock
• --auth=jwt is now the default. --auth=none requires the operator to explicitly set `OSCTRL_INSECURE_NO_AUTH=1`; a 60s warning ticker keeps the deployment from drifting into "auth-off forever".
• HttpOnly + Secure cookie session for browser/SPA clients (osctrl_token). CLI clients using Authorization: Bearer continue to work unchanged.
• Double-submit CSRF (osctrl_csrf cookie + X-CSRF-Token header) for mutating cookie-authenticated requests. CLI Bearer flows are exempt.
• JWT signing algorithm pinned to HMAC (rejects alg:none and RS-vs-HS confusion).
• JWT secret minimum 32 bytes. Startup fails fast with the openssl rand -base64 48 one-liner if the configured secret is shorter.
• Strict forwarded-headers trust via new --trusted-proxies flag. Empty default means utils.GetIP ignores X-Forwarded-For / X-Real-IP, so an internet attacker can't spoof IPs to defeat rate limits or poison audit logs.
Env secret containment + cross-env defense
• New types.TLSEnvironmentView low-privilege projection. Omits Secret, EnrollSecretPath, RemoveSecretPath, Certificate, Flags, and every other field that materially contributes to enrolling a node.
• EnvironmentHandler branches on access level: AdminLevel (or super-admin) gets the full storage struct, UserLevel gets the safe projection.
• EnvEnrollHandler / EnvRemoveHandler raised from UserLevel to AdminLevel — both embed the env's enroll/remove secret in their responses.
• EnvActionsHandler `create`/`delete` branches validate caller-supplied UUIDs via EnvUUIDFilter and ExistsByUUID.
• QueryResultsHandler now precheck-validates the named query belongs to the path env. Without this, a user with QueryLevel on env A could pull results from env B by passing B's query name in A's URL.
TLS-side hardening
• New pkg/ratelimit token-bucket middleware with per-key (IP, IP+env, etc.) buckets, amortized eviction, and a shared overflow bucket past the per-key cap so memory stays bounded under spray.
• Per-(remote, env) rate limits applied to enroll + config endpoints (--rate-limit-rps, --rate-limit-burst, --rate-limit-window).
• Constant-time comparison for env enroll-secret to defeat timing oracles.
• Two new audit hooks: AuditLog.FailedLogin (api-side) and AuditLog.FailedEnroll (tls-side) so SOC tooling has a clean stream for brute-force / password-spray / enroll-abuse alerts.
SQL hardening + carve path safety
• New carves.ValidCarvePath regexp gate. Without this a CarveLevel operator could pass '; SELECT 1; -- and pivot "carve a file" into "run any SELECT against your fleet" via `GenCarveQuery`'s string-concat path.
Authz + audit-log hardening
• bcrypt cost raised from default (10) to 12. CheckLoginCredentials opportunistically re-hashes existing users at next login — no password reset required, rehash failure is non-fatal.
• New UserManager.ClearToken empties APIToken AND CSRFToken together so an existing JWT+CSRF pair stops validating in lockstep.
• Settings + env-action handlers tightened (env-scoped settings reads instead of global; env service-name validation).
• `pkg/environments/env-cache.go`: amortized cache eviction (dropped a dead field; cache entries TTL out instead of growing unbounded).
Defaults + ops
• --audit-log default flipped to true across deploy/config/{api,admin,tls}.yml. Operators can opt out with --audit-log=false.
⚠️ Operator-visible behavior changes
Two intentional breaking changes worth a release-note line:
1. --auth=jwt is now the default. Deployments currently running --auth=none will fail to start unless they explicitly set OSCTRL_INSECURE_NO_AUTH=1. The help text and a 60s warning ticker make this loud. Intended forcing-function so insecure defaults don't survive an upgrade.
2. bcrypt cost 12 + on-login auto-rehash. Existing cost-10 hashes migrate transparently the next time each user logs in. No password reset needed, no DB migration. Login latency for affected users will tick up by ~tens of ms once, then stay at the new cost.
Test plan
• go build ./... — clean
• go vet ./... — clean
• go test ./... — all packages pass
• gofmt -l ./... — empty
• End-to-end smoke against a Kali docker deployment (login, CSRF, enroll, env-secret-containment regression)
• Pre-existing tests still pass (no behavior changes on the happy path for existing CLI Bearer clients)
• New tests added:
• cmd/api/auth_test.go — JSON-vs-redirect on 401
• cmd/api/handlers/environments_test.go — env-projection strips every secret-bearing field
• pkg/carves/utils_test.go — ValidCarvePath accept/reject table + carve-query happy-path shape
• pkg/ratelimit/ratelimit_test.go — token-bucket + eviction + overflow
What this is part of
This is round 1 of a 3-round contribution. Round 2 will add the new API endpoints the SPA needs (paginated lists, stats, saved-queries CRUD, users/permissions/tokens, env-config PATCH, audit-log filters). Round 3 will land the React frontend under a new frontend/ directory. Splitting reduces review surface per PR.
jmpsec/osctrlGitHub
05/15/2026, 1:37 PM