<#813 osctrl-api: security hardening — auth bedroc...
# osctrl
g
#813 osctrl-api: security hardening — auth bedrock, env secret containment, TLS-side rate-limit Pull request opened by alvarofraguas Summary Server-side security hardening across the API and TLS surfaces of
osctrl-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/osctrl