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
24 changes: 14 additions & 10 deletions .github/renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,22 @@
],
packageRules: [
{
// The Renovate runner image (bfra-me/renovate-action) curl-installs Bun
// globally as root and runs Renovate as a non-root user, so the bun
// manager's lockfile regeneration (`install-tool bun <ver>` →
// updateArtifacts) fails EACCES writing the root-owned containerbase tool
// dir — failing the `renovate/artifacts` check on every branch, for any
// version. Skip Renovate's bun artifact update; the `postUpgradeTasks`
// `bun install` below regenerates bun.lock on the runner's installed Bun,
// so the lockfile still stays current. Dependency update PRs are
// unaffected. Remove if the runner switches to binarySource=global.
matchManagers: ['bun'],
// bfra-me/renovate-action sets RENOVATE_BINARY_SOURCE=install, so
// Renovate's built-in artifact update path calls `install-tool bun <ver>`
// before regenerating bun.lock. That fails in this environment; the
// postUpgradeTasks `bun install` below regenerates bun.lock instead.
matchManagers: ['bun', 'npm'],
skipArtifactsUpdate: true,
},
{
// @types/node must not be grouped with Docker/custom-manager Node surfaces:
// skipArtifactsUpdate only suppresses artifact regeneration when every
// upgrade in the group carries the flag, so mixed-manager Node groups still
// hit the failing bun installer path.
matchManagers: ['npm'],
matchPackageNames: ['@types/node'],
groupName: null,
},
{matchFileNames: ['.github/workflows/**'], semanticCommitType: 'ci'},
{matchDatasources: ['docker'], semanticCommitType: 'build'},
{
Expand Down
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
24.17.0
24.18.0
6 changes: 4 additions & 2 deletions bun.lock

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

9 changes: 9 additions & 0 deletions deploy/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,15 @@ services:
- gateway-net
- sandbox-net
restart: unless-stopped
# Container hardening (#1053). The gateway binds the operator listener on an
# unprivileged port (4000) and its healthcheck uses `nc -z` (TCP connect, not
# a raw socket), so it needs no added capabilities — drop the full set. Root
# filesystem read-only and non-root user are deferred (the runtime writes a
# readiness flag to /var/run/fro-bot; that needs a tmpfs/write-path audit first).
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
logging:
driver: json-file
options:
Expand Down
4 changes: 2 additions & 2 deletions deploy/gateway.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ── Stage 1: build ────────────────────────────────────────────────────────────
FROM node:24.17.0-alpine@sha256:156b55f92e98ccd5ef49578a8cea0df4679826564bad1c9d4ef04462b9f0ded6 AS build
FROM node:24.18.0-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd AS build

WORKDIR /workspace

Expand Down Expand Up @@ -100,7 +100,7 @@ RUN rm -rf node_modules apps/*/node_modules packages/*/node_modules \
&& bun install --production --frozen-lockfile --ignore-scripts

# ── Stage 2: runtime ──────────────────────────────────────────────────────────
FROM node:24.17.0-alpine@sha256:156b55f92e98ccd5ef49578a8cea0df4679826564bad1c9d4ef04462b9f0ded6 AS runtime
FROM node:24.18.0-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd AS runtime

WORKDIR /app

Expand Down
65 changes: 65 additions & 0 deletions deploy/validate-stack.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4656,6 +4656,71 @@ echo ""
echo " OP-12 output (stderr+stdout combined):"
echo "${OP12_OUTPUT}" | sed 's/^/ /'

# ---------------------------------------------------------------------------
# TEST 68 — Positive regression: real deploy/compose.yaml gateway service must
# declare cap_drop: [ALL] and security_opt: [no-new-privileges:true]
# (#1053 container hardening).
#
# This test asserts directly against the real compose.yaml so that removing
# either hardening key from the gateway service causes an immediate test
# failure. It does NOT use validate-stack.sh (which does not check cap_drop/
# security_opt on the gateway); instead it inspects the raw YAML file with
# grep, matching the exact indented forms that Compose requires.
# ---------------------------------------------------------------------------
echo ""
echo "--- TEST 68: gateway service in real compose.yaml declares cap_drop: [ALL] and security_opt: [no-new-privileges:true] (#1053) ---"

REAL_COMPOSE_FILE="deploy/compose.yaml"

# #given the real compose.yaml exists
# #when we inspect the GATEWAY service block (not the whole file) for cap_drop
# and security_opt. Extract only the gateway service block — from the ` gateway:`
# line up to the next top-level ` <service>:` line — so the assertions cannot
# false-pass on keys that belong to another service (workspace/mitmproxy).
GW_BLOCK="$(awk '
/^ gateway:[[:space:]]*$/ { in_block = 1; next }
in_block && /^ [a-zA-Z0-9_-]+:[[:space:]]*$/ { in_block = 0 }
in_block { print }
' "${REAL_COMPOSE_FILE}")"

# cap_drop: - ALL (4-space indent for key, 6-space for list item)
GW_CAP_DROP_EXIT=0
grep -q "^ cap_drop:" <<<"${GW_BLOCK}" || GW_CAP_DROP_EXIT=$?
GW_CAP_DROP_ALL_EXIT=0
grep -q "^ - ALL" <<<"${GW_BLOCK}" || GW_CAP_DROP_ALL_EXIT=$?

# security_opt: - no-new-privileges:true (4-space indent for key, 6-space for list item)
GW_SECOPT_EXIT=0
grep -q "^ security_opt:" <<<"${GW_BLOCK}" || GW_SECOPT_EXIT=$?
GW_SECOPT_NNP_EXIT=0
grep -q "^ - no-new-privileges:true" <<<"${GW_BLOCK}" || GW_SECOPT_NNP_EXIT=$?

# #then both hardening keys must be present in the gateway service

if [[ "${GW_CAP_DROP_EXIT}" -eq 0 ]]; then
pass "TEST 68: real compose.yaml gateway service declares 'cap_drop:'"
else
fail "TEST 68: real compose.yaml gateway service is MISSING 'cap_drop:' — add cap_drop: [ALL] (#1053)"
fi

if [[ "${GW_CAP_DROP_ALL_EXIT}" -eq 0 ]]; then
pass "TEST 68: real compose.yaml gateway service declares 'cap_drop: - ALL'"
else
fail "TEST 68: real compose.yaml gateway service is MISSING '- ALL' under cap_drop — add cap_drop: [ALL] (#1053)"
fi

if [[ "${GW_SECOPT_EXIT}" -eq 0 ]]; then
pass "TEST 68: real compose.yaml gateway service declares 'security_opt:'"
else
fail "TEST 68: real compose.yaml gateway service is MISSING 'security_opt:' — add security_opt: [no-new-privileges:true] (#1053)"
fi

if [[ "${GW_SECOPT_NNP_EXIT}" -eq 0 ]]; then
pass "TEST 68: real compose.yaml gateway service declares 'security_opt: - no-new-privileges:true'"
else
fail "TEST 68: real compose.yaml gateway service is MISSING '- no-new-privileges:true' under security_opt — add security_opt: [no-new-privileges:true] (#1053)"
fi

# ---------------------------------------------------------------------------
# LOCKSTEP-1 — Negative/Positive: egress-smoke.sh must not hardcode the
# mitmproxy image; it must derive it from deploy/compose.yaml.
Expand Down
4 changes: 2 additions & 2 deletions deploy/workspace.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# packages/runtime/src/shared/constants.ts.

# ── Stage 1: build ────────────────────────────────────────────────────────────
FROM node:24.17.0-alpine@sha256:156b55f92e98ccd5ef49578a8cea0df4679826564bad1c9d4ef04462b9f0ded6 AS build
FROM node:24.18.0-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd AS build

WORKDIR /workspace

Expand Down Expand Up @@ -112,7 +112,7 @@ RUN rm -rf node_modules apps/*/node_modules packages/*/node_modules \
&& bun install --production --frozen-lockfile --ignore-scripts

# ── Stage 2: runtime ──────────────────────────────────────────────────────────
FROM node:24.17.0-alpine@sha256:156b55f92e98ccd5ef49578a8cea0df4679826564bad1c9d4ef04462b9f0ded6 AS runtime
FROM node:24.18.0-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd AS runtime

WORKDIR /app

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"@octokit/webhooks-types": "7.6.1",
"@semantic-release/exec": "7.1.0",
"@semantic-release/git": "10.0.1",
"@types/node": "24.13.1",
"@types/node": "24.13.2",
"@vitest/eslint-plugin": "1.6.20",
"conventional-changelog-conventionalcommits": "9.3.1",
"eslint": "10.5.0",
Expand Down