Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b88f5bb
Merge pull request #3 from thinkgrid-labs/dev
akosidencio Jul 4, 2026
788be1e
Merge pull request #4 from thinkgrid-labs/dev
akosidencio Jul 4, 2026
ec4f0a3
Merge pull request #5 from thinkgrid-labs/dev
akosidencio Jul 5, 2026
92f602a
Merge pull request #6 from thinkgrid-labs/dev
akosidencio Jul 18, 2026
65469df
Merge pull request #7 from thinkgrid-labs/dev
akosidencio Jul 18, 2026
919cc8f
Merge pull request #8 from thinkgrid-labs/dev
akosidencio Jul 18, 2026
5626a30
Merge pull request #9 from thinkgrid-labs/dev
akosidencio Jul 18, 2026
d836dca
Merge pull request #10 from thinkgrid-labs/dev
akosidencio Jul 18, 2026
d738ee3
Merge pull request #11 from thinkgrid-labs/dev
akosidencio Jul 18, 2026
621b6a2
Merge pull request #12 from thinkgrid-labs/dev
akosidencio Jul 18, 2026
9cd34b2
Merge pull request #13 from thinkgrid-labs/dev
akosidencio Jul 25, 2026
88e57fb
Merge pull request #14 from thinkgrid-labs/dev
akosidencio Jul 25, 2026
8d50b47
Merge pull request #15 from checkgate-dev/dev
akosidencio Aug 2, 2026
f37f215
docs: update readme
Aug 3, 2026
6594622
Merge pull request #16 from checkgate-sh/dev
akosidencio Sep 17, 2026
9b12bf0
Merge pull request #17 from checkgate-sh/dev
akosidencio Sep 17, 2026
52b0539
feat: unify dashboard indigo branding, refresh screenshots, and upgra…
akosidencio Sep 17, 2026
c93c04e
feat: unify dashboard indigo branding, refresh screenshots, and upgra…
akosidencio Sep 17, 2026
e936942
Merge pull request #18 from checkgate-sh/dev
akosidencio Sep 17, 2026
34b0987
Merge pull request #20 from checkgate-sh/dev
akosidencio Oct 18, 2026
f351cc1
fix(server): resolve client IPs via trusted proxies; per-class rate l…
akosidencio Oct 29, 2026
1e64f0b
fix(server): make first-run setup atomic and require a one-time setup…
akosidencio Oct 29, 2026
8eace21
fix(server): scope SDK health connections to the caller's projects
akosidencio Oct 29, 2026
bfff31f
fix(server): remove the write-only, key-only server flag cache
akosidencio Oct 29, 2026
4c393cb
test(server): satisfy clippy needless_range_loop in environment isola…
akosidencio Oct 29, 2026
4020782
fix(docker): honour external DATABASE_URL/REDIS_URL with explicit dep…
akosidencio Oct 29, 2026
9feeb5a
chore: stop tracking dependencies and build caches; guard against re-…
akosidencio Oct 29, 2026
8a855cd
chore: restore NOTICE and README license lines unintentionally includ…
akosidencio Oct 29, 2026
e3a32fe
fix: harden setup isolation and connection-string redaction (review p…
akosidencio Oct 29, 2026
03cc307
chore: update copyright attribution in NOTICE and README
akosidencio Oct 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ target/
Thumbs.db
.idea/
.vscode/

# A local server's one-time setup token must never reach an image.
checkgate-setup-token
50 changes: 49 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ concurrency:
cancel-in-progress: true

jobs:
# ---------------------------------------------------------------------------
# Repository hygiene — dependencies, build output and tool caches must not be
# committed; every job below installs and builds what it needs itself.
# ---------------------------------------------------------------------------
hygiene:
name: Repository hygiene
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: No generated or machine-specific files are tracked
run: scripts/check-tracked-artifacts.sh

# ---------------------------------------------------------------------------
# Rust — test the whole workspace
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -144,6 +157,10 @@ jobs:
run: |
cargo test -p server --test integration -- --nocapture
cargo test -p server --test e2e_sse -- --nocapture
cargo test -p server --test rate_limit -- --nocapture
cargo test -p server --test setup_ownership -- --nocapture
cargo test -p server --test sdk_health -- --nocapture
cargo test -p server --test environment_isolation -- --nocapture

# ---------------------------------------------------------------------------
# Native SDK e2e — build the real @checkgate/node addon and drive it over a
Expand Down Expand Up @@ -203,17 +220,48 @@ jobs:
REDIS_URL: redis://localhost:6379
run: |
PORT=3111
SETUP_TOKEN="ci-setup-token-$(openssl rand -hex 16)"
SESSION_SECRET=ci-secret-0123456789-0123456789-0123456789-0123456789 \
SETUP_TOKEN="$SETUP_TOKEN" \
COOKIE_SECURE=false PORT=$PORT PUBLIC_DIR="$(mktemp -d)" RUST_LOG=warn \
./target/debug/server > server.log 2>&1 &
SRV=$!
for i in $(seq 1 60); do curl -sf "http://127.0.0.1:$PORT/health" >/dev/null && break; sleep 1; done
CHECKGATE_URL="http://127.0.0.1:$PORT" node sdks/nodejs/e2e/e2e.cjs
CHECKGATE_URL="http://127.0.0.1:$PORT" CHECKGATE_SETUP_TOKEN="$SETUP_TOKEN" \
node sdks/nodejs/e2e/e2e.cjs
RC=$?
kill $SRV 2>/dev/null || true
if [ $RC -ne 0 ]; then echo "=== server log ==="; cat server.log; fi
exit $RC

# ---------------------------------------------------------------------------
# Docker image — build it and smoke-test both deployment modes (all-in-one
# and external PostgreSQL/Redis), restarts, persistence and refusal of
# half-specified configuration.
# ---------------------------------------------------------------------------
docker-smoke:
name: Docker image smoke tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
push: false
load: true
tags: checkgate:smoke
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Smoke-test deployment modes
run: scripts/docker-smoke.sh checkgate:smoke

# ---------------------------------------------------------------------------
# Dashboard — TypeScript build check
# ---------------------------------------------------------------------------
Expand Down
39 changes: 26 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,10 @@ jobs:
artifact: checkgate.linux-arm64-gnu.node
cross: true

# Linux x64 (musl — Alpine) — cross-compile via napi-rs cross toolchain
# Linux x64 (musl — Alpine) — cross-compile with Zig
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
artifact: checkgate.linux-x64-musl.node
cross: true

# macOS x64 — cross-compile natively from arm64 runner
- os: macos-latest
Expand All @@ -118,10 +117,19 @@ jobs:
steps:
- uses: actions/checkout@v5

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
# Install targets on the toolchain selected by rust-toolchain.toml.
# Installing them on `stable` can leave the pinned toolchain without core/std.
- name: Install Rust toolchain and target
shell: bash
run: |
rustup show
rustup target add "${{ matrix.target }}"

- name: Install Zig for musl
if: matrix.target == 'x86_64-unknown-linux-musl'
uses: mlugg/setup-zig@v2
with:
targets: ${{ matrix.target }}
version: 0.14.1

- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
Expand All @@ -141,10 +149,10 @@ jobs:
shell: bash
run: |
JS_FLAGS="--js native-binding.js --dts native-binding.d.ts"
if [ "${{ matrix.target }}" = "aarch64-unknown-linux-musl" ]; then
# Satisfy host-side NAPI-RS linker check (will use real linker inside Docker)
sudo ln -s /usr/bin/true /usr/bin/aarch64-linux-musl-gcc
npx napi build --platform --release --target ${{ matrix.target }} --use-napi-cross $JS_FLAGS
if [ "${{ matrix.target }}" = "x86_64-unknown-linux-musl" ]; then
# Zig supplies the musl runtime and unwinder that Ubuntu's
# musl-gcc wrapper lacks. NAPI also disables static CRT linkage.
npx napi build --platform --release --target ${{ matrix.target }} --cross-compile $JS_FLAGS
elif [ "${{ matrix.cross }}" = "true" ]; then
npx napi build --platform --release --target ${{ matrix.target }} --use-napi-cross $JS_FLAGS
else
Expand Down Expand Up @@ -179,10 +187,10 @@ jobs:
steps:
- uses: actions/checkout@v5

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Install Rust toolchain and target
run: |
rustup show
rustup target add wasm32-unknown-unknown

- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
Expand Down Expand Up @@ -291,6 +299,11 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max

# Both deployment modes must start, persist data and refuse
# misconfiguration before anything is published.
- name: Smoke-test deployment modes
run: scripts/docker-smoke.sh "${{ env.IMAGE_NAME }}:scan"

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
Expand Down
17 changes: 14 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
# Rust
target/

# Node / Bun
dashboard/node_modules/
docs/node_modules/
# Node / Bun — dependencies are installed by each package's own
# `npm install` / `bun install`; never commit them.
node_modules/
docs/.vitepress/dist/
server/public/
coverage/

# Dart / Flutter — tool state and build/test caches are machine-specific and
# regenerated by `flutter pub get` / `flutter test`.
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
sdks/flutter/dart/build/

# TypeScript incremental build state — regenerated by every `tsc -b`, so
# tracking it churns the diff on each commit.
Expand All @@ -22,3 +30,6 @@ docs/bun.lock
.env
.env.*
!.env.example

# One-time setup token written by a local server that has not been set up.
checkgate-setup-token
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 12 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# =============================================================================
# Checkgate — all-in-one image
# Checkgate image
#
# Bundles: PostgreSQL 16 + Redis 7 + Checkgate server + React dashboard.
# Perfect for demos, staging, or single-node self-hosted deployments.
# Bundles: PostgreSQL + Redis + Checkgate server + React dashboard.
#
# One image, two modes (see docker/entrypoint-full.sh, CHECKGATE_MODE):
# all-in-one (default) starts the bundled PostgreSQL and Redis — demos,
# staging, single-node self-hosting.
# external uses DATABASE_URL and REDIS_URL and starts neither bundled
# service — managed databases, multiple replicas. Chosen
# automatically when either URL is set.
# =============================================================================

# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -77,7 +83,8 @@
COPY docker/entrypoint-full.sh /entrypoint.sh

RUN sed -i 's/\r//' /entrypoint.sh && chmod +x /entrypoint.sh && \
mkdir -p /var/lib/postgresql/data /var/log && \
mkdir -p /var/lib/postgresql/data /var/log /var/lib/checkgate /var/lib/redis && \
chmod 700 /var/lib/checkgate && \
chown -R postgres:postgres /var/lib/postgresql && \
touch /var/log/postgresql.log /var/log/redis.log && \
chown postgres:postgres /var/log/postgresql.log /var/log/redis.log
Expand All @@ -88,7 +95,8 @@
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1

ENV PUBLIC_DIR=/app/public \

Check warning on line 98 in Dockerfile

View workflow job for this annotation

GitHub Actions / Docker image smoke tests

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "POSTGRES_PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
SETUP_TOKEN_FILE=/var/lib/checkgate/setup-token \
PGDATA=/var/lib/postgresql/data \
POSTGRES_USER=checkgate \
POSTGRES_PASSWORD=checkgate \
Expand Down
4 changes: 2 additions & 2 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Checkgate
Copyright 2025 ThinkGrid Labs
Copyright 2025

This product includes software developed at ThinkGrid Labs
This product includes software developed
(https://github.com/checkgate-sh/checkgate).
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,6 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).

## License

Apache License 2.0 — © 2026 ThinkGrid Labs
Apache License 2.0 — © 2025


9 changes: 5 additions & 4 deletions dashboard/scripts/capture-screenshots.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const baseURL = process.env.CHECKGATE_SCREENSHOT_BASE_URL ?? 'http://127.0.0.1:3
const output = (process.env.CHECKGATE_SCREENSHOT_OUTPUT
?? fileURLToPath(new URL('../../assets/screenshots/', import.meta.url))).replace(/\/$/, '')
const staging = `${output}.capture`
const setupToken = process.env.CHECKGATE_SCREENSHOT_SETUP_TOKEN ?? 'checkgate-screenshot-demo-setup-token'
const workspaceName = 'Vantage Robotics'
const projectName = 'Vantage Mobile'
const password = 'checkgate-screenshot-demo-password'
Expand Down Expand Up @@ -44,10 +45,10 @@ try {
extraHTTPHeaders: { 'X-Checkgate-Request': 'true' },
})

async function api(path, method = 'GET', data, request = context.request) {
// Respect the server's 60-request-per-minute API quota while seeding.
async function api(path, method = 'GET', data, request = context.request, headers = {}) {
// Stay well inside the server's per-client API quota while seeding.
await new Promise(resolve => setTimeout(resolve, 1050))
const response = await request.fetch(path, { method, data })
const response = await request.fetch(path, { method, data, headers })
if (!response.ok()) throw new Error(`${method} ${path}: ${response.status()} ${await response.text()}`)
return response.status() === 204 ? undefined : response.json()
}
Expand All @@ -56,7 +57,7 @@ try {
if (!workspace.is_setup_complete) {
await api('/api/setup/complete', 'POST', {
workspace_name: workspaceName, project_name: projectName, ...admin,
})
}, context.request, { 'X-Checkgate-Setup-Token': setupToken })
} else {
assert.equal(workspace.workspace_name, workspaceName, 'Expected the disposable screenshot workspace')
await api('/api/auth/login', 'POST', admin)
Expand Down
15 changes: 13 additions & 2 deletions dashboard/src/context/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ interface AuthContextValue {
name: string,
email: string,
password: string,
setupToken: string,
) => Promise<{ ok: boolean; error?: string }>
}

Expand Down Expand Up @@ -167,12 +168,16 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}, [])

const completeSetup = useCallback(
async (workspaceName: string, projectName: string, name: string, email: string, password: string) => {
async (workspaceName: string, projectName: string, name: string, email: string, password: string, setupToken: string) => {
try {
const res = await fetch('/api/setup/complete', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', 'X-Checkgate-Request': 'true' },
headers: {
'Content-Type': 'application/json',
'X-Checkgate-Request': 'true',
'X-Checkgate-Setup-Token': setupToken,
},
body: JSON.stringify({
workspace_name: workspaceName.trim(),
project_name: projectName.trim() || 'My App',
Expand All @@ -185,6 +190,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
if (res.status === 404) {
return { ok: false, error: 'Setup already complete. Please log in.' }
}
if (res.status === 401) {
return { ok: false, error: 'The setup token is not valid. Go back to the first step and re-enter it.' }
}
if (res.status === 409) {
return { ok: false, error: 'An account with this email already exists. Use a different email.' }
}
if (res.status === 422) {
return { ok: false, error: 'Password must be at least 8 characters.' }
}
Expand Down
7 changes: 6 additions & 1 deletion dashboard/src/pages/SdkHealth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ export default function SdkHealth() {
<div>
<h1 className="text-2xl font-bold text-gray-900">SDK Health</h1>
<p className="text-gray-500 text-sm mt-1">
Live SSE connections to this server instance. Refreshes every 10 s.
Live SSE connections to this server instance, for the projects you can access.
Refreshes every 10 s.
</p>
<p className="text-gray-400 text-xs mt-1">
Each instance tracks its own connections. Client addresses are shown as a network
prefix unless you are a workspace admin.
</p>
</div>
<button
Expand Down
Loading
Loading