Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: CI

on:
push:
branches: ["**"]
pull_request:
branches: ["master", "main"]

jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.23"

- name: Build
run: go build ./...

- name: Vet
run: go vet ./...

- name: Check formatting
run: |
FILES=$(gofmt -l $(find . -name '*.go' | grep -v '^./rad/' | grep -v '^./vendor/'))
if [ -n "$FILES" ]; then
echo "These files are not gofmt-clean:"
echo "$FILES"
exit 1
fi

- name: Install staticcheck
run: go install honnef.co/go/tools/cmd/staticcheck@latest

- name: Staticcheck
run: staticcheck ./...

- name: Test with race detector
run: go test -race -timeout 120s ./...
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
*.so
*.dylib

# Compiled binary outputs
bin/
dist/
goboxd
/goboxd

# Test binary, built with `go test -c`
*.test

Expand All @@ -30,3 +36,10 @@ go.work.sum
# Editor/IDE
# .idea/
# .vscode/

# Planning docs and AI journals (not submission artifacts)
rad/
docs/ai/

# Tool caches
.tools/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "external/nsjail"]
path = external/nsjail
url = https://github.com/google/nsjail
18 changes: 17 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ WORKDIR /src
COPY go.mod ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/goboxd ./cmd/goboxd
RUN CGO_ENABLED=0 go build -trimpath \
-ldflags="-s -w \
-X goboxd/internal/api.Version=$(git describe --tags --always --dirty 2>/dev/null || echo dev) \
-X goboxd/internal/api.Commit=$(git rev-parse --short HEAD 2>/dev/null || echo unknown) \
-X goboxd/internal/api.GoVersion=$(go env GOVERSION)" \
-o /out/goboxd ./cmd/goboxd

# ---- Runtime image ----
FROM debian:${DEBIAN_VERSION}-slim AS runtime
Expand All @@ -35,5 +40,16 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
COPY --from=nsjail-builder /usr/local/bin/nsjail /usr/local/bin/nsjail
COPY --from=builder /out/goboxd /usr/local/bin/goboxd
COPY configs ./configs
COPY scripts/lang_install ./scripts/lang_install

# Install all language toolchains. Each script must exit 1 on failure.
# apt-get update runs once before the loop.
RUN apt-get update && \
for f in ./scripts/lang_install/*.sh; do \
echo "=== Installing: $f ===" && sh "$f" || exit 1; \
done && \
rm -rf /var/lib/apt/lists/*

EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/goboxd"]
50 changes: 41 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,19 +1,51 @@
.PHONY: build run test integration lint
.PHONY: build run test integration load lint docker-build docker-run check clean

BIN := bin/goboxd
COMPOSE ?= docker compose
TOOLS := $(COMPOSE) --profile tools run --rm tools
GOBOXD_URL ?= http://localhost:8080

build:
$(COMPOSE) build goboxd
@mkdir -p bin
go build -trimpath -o $(BIN) ./cmd/goboxd

run:
$(COMPOSE) up goboxd
run: build
./$(BIN)

test:
$(TOOLS) go test ./...
go test -race ./...

lint:
go vet ./...
@FILES=$$(gofmt -l $$(find . -name '*.go' | grep -v '^./rad/' | grep -v '^./vendor/')); \
if [ -n "$$FILES" ]; then echo "gofmt: unformatted files:"; echo "$$FILES"; exit 1; fi
@command -v staticcheck >/dev/null && staticcheck ./... || echo "staticcheck not installed; skipping"

# integration: bring the container up, wait for /readyz, run tests/ with
# the integration build tag against the live server, then tear down.
integration:
$(TOOLS) go test -tags=integration ./tests/...
$(COMPOSE) up -d --build
@echo "waiting for $(GOBOXD_URL)/readyz ..."
@for i in $$(seq 1 60); do \
if curl -fsS $(GOBOXD_URL)/readyz >/dev/null 2>&1; then break; fi; \
sleep 2; \
done
GOBOXD_URL=$(GOBOXD_URL) go test -tags=integration -v ./tests/...
$(COMPOSE) down

lint:
$(TOOLS) golangci-lint run ./...
# load: run the load test suite against a running server.
# Server must already be up (via 'make docker-run' or 'make run').
load:
@bash scripts/load/run.sh

# check: ultimate pre-submission verification (phases A through H).
check:
@bash testdata/check.sh

docker-build:
docker build -t goboxd:dev .

docker-run:
$(COMPOSE) up --build

clean:
rm -rf bin dist
59 changes: 59 additions & 0 deletions PR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## HUGO

**Team**: Shivam Mishra

## Framework

Go's `net/http` with `http.ServeMux` (Go 1.22 method-prefixed routes) — no
framework dependencies, fewer moving parts to audit when the whole point is
sandbox isolation.

## How to run locally

```sh
git clone <repo> && cd goboxd
make docker-run # build image + start container
curl localhost:8080/healthz # should return {"status":"ok"}
curl localhost:8080/readyz # per-language status
curl localhost:8080/info # build info + stats
make test # unit tests with race detector
make check # full pre-submission check suite
make load # load test (needs server running)
```

All make targets work from a fresh clone in under 10 minutes including the
Docker build. No bare `go run` required.

## Security holes closed

7 holes closed per spec §06. All file:line references resolve to real lines.
See [docs/security.md](docs/security.md) for the full breakdown.

| # | Hole | File:line |
|---|------|-----------|
| 1 | Path traversal via filename | [internal/validator/validator.go:115](internal/validator/validator.go#L115) |
| 2 | Shell-style directory commands | [internal/runner/workspace.go:24](internal/runner/workspace.go#L24) |
| 3 | Compiler-flag injection (denylist: `-fplugin`, `-x`, `-B`, `--specs`, `-Wl,`, `@`, `-I/`) | [internal/validator/validator.go:28](internal/validator/validator.go#L28) |
| 4 | Dual-layer size caps (HTTP 1 MiB + validator source/stdin/expected_stdout + rlimit_fsize) | [internal/api/server.go:209](internal/api/server.go#L209), [internal/validator/validator.go:63](internal/validator/validator.go#L63) |
| 5 | Workspace UID uniqueness (PID + random suffix via os.MkdirTemp) | [internal/runner/workspace.go:23](internal/runner/workspace.go#L23) |
| 6 | Unbounded child output (capBuffer 64 KiB, returns len(p) on truncation) | [internal/jail/execute_linux.go:17](internal/jail/execute_linux.go#L17) |
| 7 | Stale jail directories (defer cleanup + background sweeper) | [internal/runner/workspace.go:35](internal/runner/workspace.go#L35), [internal/runner/sweeper.go:74](internal/runner/sweeper.go#L74) |

## Languages supported

**7 in-scope** (spec §02): `bash`, `c`, `cpp`, `java`, `javascript`, `py3`, `verilog`

**4 beyond-seven** (bonus): `go`, `lua`, `ruby`, `rust`

All languages configured in `configs/languages.yaml`. Adding a new language
requires one YAML block + one install script — no Go code changes. Boot
validation aborts with a clear error if any toolchain is missing.

See [docs/languages.md](docs/languages.md) for the timed demo-add drill.

## Benchmarks

See [docs/benchmarks.md](docs/benchmarks.md) for the full results table and
acceptance bars (p99 < 5× p50, 0% 5xx, RSS < 512 MiB).

Load test: `make load` (runs `scripts/load/run.sh` against the live container).
95 changes: 43 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,70 +1,61 @@
<div align="center">

# goboxd

**A Go HTTP service for executing untrusted code in isolated sandboxes.**

[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE)
[![Go](https://img.shields.io/badge/Go-1.23-00ADD8.svg?logo=go&logoColor=white)](https://go.dev)
[![Docker](https://img.shields.io/badge/Docker-Required-2496ED.svg?logo=docker&logoColor=white)](https://www.docker.com)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/thesouldev/goboxd/pulls)

</div>

---

## Overview

goboxd is an HTTP service written in Go that compiles and runs untrusted code inside isolated sandboxes and returns the result. Optional test cases can be supplied to assert behaviour against expected output. It is built for safe execution of code across many languages, with strict isolation, bounded concurrency, and a plug and play language registry.

## Features
A Go HTTP service that runs untrusted code inside an nsjail sandbox and
returns per-test results. Hackathon submission for SEEK x Paradox IIT
Madras 2026.

- Plug and play language registry driven by YAML
- Process isolation using Linux namespaces and cgroups
- Bounded concurrency with request queuing
- Fully containerised for local development and deployment
- Per request resource limits for time, memory, and processes
- Liveness and readiness probes for orchestration
## Framework

## Getting started
`net/http` with `http.ServeMux` (Go 1.22 method-prefixed routes). No
framework dependency, no router middleware tower, fewer moving parts to
audit when the spec's whole point is sandbox isolation and security.

### Prerequisites
## Status

- Docker with Compose v2
Working end to end for Python 3 and C++ inside the container. All seven
documented security holes are closed (see `docs/security.md`). `/healthz`,
`/readyz`, and `/info` are live. Concurrency is bounded with a
configurable queue.

No Go toolchain or system dependencies are required on the host. Everything runs in containers.
## Run it

### Installation

```sh
git clone https://github.com/thesouldev/goboxd.git
cd goboxd
make build
```

### Usage

```sh
make run # start the service on :8080
make test # run unit tests
make integration # run end to end tests
make lint # run static analysis
git submodule update --init
make docker-run
```

## Project structure
Then:

```
.
├── cmd/goboxd/ binary entry point
├── internal/ private application packages
├── docs/ api, languages, security, benchmarks, architecture
└── tests/ integration tests
curl -s localhost:8080/healthz
curl -s localhost:8080/readyz
curl -s -X POST localhost:8080/run \
-H 'content-type: application/json' \
--data @testdata/py-hello.json
```

## Contributing
## Layout

Contributions are welcome. Open an issue to discuss substantial changes before sending a pull request.
- `cmd/goboxd` - binary entry point
- `internal/` - `types`, `config`, `validator`, `limiter`, `jail`,
`runner`, `health`, `logging`, `api`
- `configs/` - `server.yaml` and `languages.yaml`
- `external/nsjail` - git submodule pinned to upstream tag `3.4`, built
inside the image
- `docs/` - `api.md`, `languages.md`, `security.md`, `benchmarks.md`,
`architecture.md`
- `tests/` - black-box HTTP end-to-end tests (build tag `integration`)
- `testdata/` - sample request bodies
- `scripts/load.sh` - vegeta load probe
- `testdata/check.sh` - full pre-submission verification (phases A-H)

## License
## Develop

```
make build test lint
```

This project is distributed under the GNU General Public License v3.0. See [LICENSE](LICENSE) for the full text.
`make test` is unit-only and runs on any OS. `make integration` brings
the container up and runs the `tests/` suite against it. `make check`
runs the full pre-submission verification script.
See `docs/architecture.md` for the design walk-through and `docs/benchmarks.md` for concurrency numbers.
Loading