Repository navigation
Close the paths that let strangers run code on the home VM - #5
Merged
Merged
Conversation
Public registration was on by default, any registered user could deploy any image, and the deploy workflow ran it on the self-hosted runner with no resource limits. Together, anyone who could reach the API could run arbitrary containers on the home network. - Registration is off by default. Accounts are created with python -m app.admin create, which now also creates the tables on a fresh database. - Only admins deploy images outside the platform catalog. The catalog moved to services/catalog.py; the policy is checked when an application is registered, when it is deployed (covering applications registered before this change) and on the Kubernetes deployments path, which now returns 400/403 instead of a 500 for a bad image. - Every container the deploy workflow starts gets memory, CPU and process limits (repository variables, defaults 512m / 1 / 512) and no-new-privileges. - Tokens are never signed with a key published in this repository. Outside production/prod/staging, including the default "local", an unset or placeholder SECRET_KEY used to sign tokens with "change-me-in-production"; it is now replaced by a random per-process key. Production-like environments refuse to start. - Expired sandboxes were never removed: the sweeper ran docker rm from an API container that has no Docker, then marked them expired anyway. With dispatch configured it now triggers the new Teardown Sandbox workflow on the runner, and a failed request leaves the sandbox active so the next sweep retries. A manual run with deployment_id=all removes containers leaked by older versions. - The anonymous sandbox demo is capped at MAX_ACTIVE_SANDBOXES (default 3, 0 disables it) and answers 429 with Retry-After when full. A sandbox whose dispatch fails is marked failed so it frees its slot. - Kubernetes operations and cluster health/pod logs are admin only. - Workflow input checks written as "[[ regex ]] && (( range ))" were skipped when the regex failed, because bash -e ignores failures inside an && list. Each check is now its own line.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Three defaults combined into one serious hole. Public registration was on. Any registered user could deploy any image.
deploy-application.yamlran that image on the self-hosted runner with--restart unless-stoppedand no resource limits. Anyone who could reach the API could therefore run arbitrary containers on the home network.This PR also fixes a token-forgery path, sandbox containers that were never removed, and cluster-wide Kubernetes access for every logged-in user.
Changes
Who can deploy what
python -m app.admin create, which now also creates the tables on a fresh database (it used to fail withno such table: users).services/catalog.py. The policy is checked when an application is registered, again when it's deployed (so applications registered before this change can't slip through), and on the Kubernetes/deploymentspath. A bad image there now returns 400 or 403 instead of a 500.no-new-privileges. Defaults are512m,1CPU and512processes, adjustable with the repository variablesCONTAINER_MEMORY_LIMIT,CONTAINER_CPU_LIMITandCONTAINER_PIDS_LIMIT.kube-system.Login tokens
production,prodandstaging, including the defaultlocal, an unsetSECRET_KEYmeant tokens were signed withchange-me-in-production. Anyone could then forge a token for user 1. Unset or placeholder keys are now replaced by a random per-process key, so sessions end when the app restarts until a real key is set. Production-like environments still refuse to start without a strong key, and now also reject the.env.exampleplaceholder.Sandboxes
docker rminside the API container, which has no Docker CLI or socket. It then marked each sandbox expired anyway, so the containers kept running on the VM. When dispatch is configured, the sweeper now triggers the new Teardown Sandbox workflow on the runner. If that request fails, the sandbox stays active and the next sweep retries.MAX_ACTIVE_SANDBOXES. The default is 3, and 0 disables the demo. When all slots are full it answers 429 withRetry-After. A sandbox whose dispatch fails is marked failed, so it doesn't hold a slot.Workflow input validation
[[ regex ]] && (( range ))were skipped whenever the regex failed:bash -eignores a failure that isn't the last command in an&&list. For example,HOST_PORT="80; rm -rf x"passed validation. Each check now sits on its own line, in bothdeploy-application.yamlandsandbox-deploy.yml.After merging
SECRET_KEYin theAPP_ENVsecret (openssl rand -hex 32). If it still contains the.env.exampleplaceholder andENVIRONMENTis production-like, the new image fails its health check and the deploy job rolls back to the previous container. Otherwise, users are logged out on every restart.deployment_id=all. This also stops any sandbox running at that moment.services/catalog.py.Testing
tests/unit/test_security_hardening.py. flake8 is clean using the same command as CI..envand noSECRET_KEY:app.admin createworked.kube-systemreturned 403 for the regular user.bash -escripts: they accept valid input and rejectHOST_PORT='80;x', a bad memory limit and a JSON-injecting TTL.docker. They remove only that deployment's labelled container, remove every sandbox forallon a manual run, handle "nothing to remove", and refuseallfromrepository_dispatchas well as a malformed ID.Next PRs
GETendpoints.