diff --git a/Makefile b/Makefile index 493bbcf5a5..bf88ace2ba 100644 --- a/Makefile +++ b/Makefile @@ -265,15 +265,25 @@ _build-with-docker: # Internal target for Docker-based cross-compilation exit 1; \ fi -docker-image: build-ui ## Build Docker image +docker-image: build-ui ## Build Docker image (LOCAL=1 to use Dockerfile.local) @echo "$(GREEN)Building Docker image...$(NC)" $(eval GIT_SHA=$(shell git rev-parse --short HEAD)) - @docker build -f transports/Dockerfile -t bifrost -t bifrost:$(GIT_SHA) -t bifrost:latest . - @echo "$(GREEN)Docker image built: bifrost, bifrost:$(GIT_SHA), bifrost:latest$(NC)" + $(eval DOCKERFILE=$(if $(LOCAL),transports/Dockerfile.local,transports/Dockerfile)) + @docker build -f $(DOCKERFILE) -t bifrost -t bifrost:$(GIT_SHA) -t bifrost:latest . + @echo "$(GREEN)Docker image built: bifrost, bifrost:$(GIT_SHA), bifrost:latest (using $(DOCKERFILE))$(NC)" -docker-run: ## Run Docker container +docker-run: ## Run Docker container (Usage: make docker-run [CONFIG=path/to/config.json or path/to/dir/]) @echo "$(GREEN)Running Docker container...$(NC)" - @docker run -e APP_PORT=$(PORT) -e APP_HOST=0.0.0.0 -p $(PORT):$(PORT) -e LOG_LEVEL=$(LOG_LEVEL) -e LOG_STYLE=$(LOG_STYLE) -v $(shell pwd):/app/data bifrost + @CONFIG_PATH="$(abspath $(CONFIG))"; \ + if [ -n "$(CONFIG)" ]; then \ + if [ -d "$$CONFIG_PATH" ]; then \ + CONFIG_PATH="$$CONFIG_PATH/config.json"; \ + fi; \ + CONFIG_MOUNT="-v $$CONFIG_PATH:/app/data/config.json"; \ + else \ + CONFIG_MOUNT=""; \ + fi; \ + docker run -e APP_PORT=$(PORT) -e APP_HOST=0.0.0.0 -p $(PORT):$(PORT) -e LOG_LEVEL=$(LOG_LEVEL) -e LOG_STYLE=$(LOG_STYLE) -v $(shell pwd):/app/data $$CONFIG_MOUNT bifrost docs: ## Prepare local docs @echo "$(GREEN)Preparing local docs...$(NC)" diff --git a/config.json b/config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/docs.json b/docs/docs.json index 88c589ac0a..c4fb9be8e6 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -451,6 +451,13 @@ } ] }, + { + "tab": "Security", + "icon": "shield", + "pages": [ + "security" + ] + }, { "tab": "Benchmarks", "icon": "chart-line", diff --git a/docs/media/security/codeowners.png b/docs/media/security/codeowners.png new file mode 100644 index 0000000000..de6f881b80 Binary files /dev/null and b/docs/media/security/codeowners.png differ diff --git a/docs/media/security/codeql.png b/docs/media/security/codeql.png new file mode 100644 index 0000000000..ac02a8c694 Binary files /dev/null and b/docs/media/security/codeql.png differ diff --git a/docs/media/security/dep-pinning.png b/docs/media/security/dep-pinning.png new file mode 100644 index 0000000000..6369e279e0 Binary files /dev/null and b/docs/media/security/dep-pinning.png differ diff --git a/docs/media/security/hardned-base-image.png b/docs/media/security/hardned-base-image.png new file mode 100644 index 0000000000..777af9a5f4 Binary files /dev/null and b/docs/media/security/hardned-base-image.png differ diff --git a/docs/media/security/scout-image-score.png b/docs/media/security/scout-image-score.png new file mode 100644 index 0000000000..4e3355fc97 Binary files /dev/null and b/docs/media/security/scout-image-score.png differ diff --git a/docs/media/security/step-security.png b/docs/media/security/step-security.png new file mode 100644 index 0000000000..1d85b04dd3 Binary files /dev/null and b/docs/media/security/step-security.png differ diff --git a/docs/security.mdx b/docs/security.mdx new file mode 100644 index 0000000000..c04145de54 --- /dev/null +++ b/docs/security.mdx @@ -0,0 +1,289 @@ +--- +title: "Security at Bifrost" +description: "Overview of security practices across Bifrost's CI/CD pipelines, container images, supply chain, and deployment infrastructure." +icon: "shield" +sidebarTitle: "Security" +--- + +Bifrost applies defense-in-depth across its open-source and enterprise repositories. Every pull request, +dependency update, and container image goes through multiple layers of automated security checks before +reaching production. + +| Domain | Tool / Practice | Coverage | +| --- | --- | --- | +| Dependency Scanning | Snyk Open Source | Go, Node, Python — all projects | +| SAST | Snyk Code, CodeQL | Full codebase static analysis | +| Container Scanning | Docker Scout | Docker Hub auto-scan on push | +| Artifact Scanning | GCP Artifact Registry | Enterprise container images | +| Dependency Updates | Dependabot | gomod, npm, Docker, GitHub Actions | +| Supply Chain | SHA pinning, npm provenance | 100 % of GitHub Actions (OSS) | +| Container Hardening | FIPS base image, non-root user | Production Dockerfile | +| Security Hardening | StepSecurity, CODEOWNERS | Workflow hardening, code review gates | +| Network Security | Tailscale VPN | Enterprise deployments | + +--- + +## Vulnerability Scanning — Snyk + +Bifrost runs two Snyk scanning jobs on every push and pull request. Results are uploaded as +[SARIF](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning) +to the GitHub Security tab. + + + + The **Snyk Open Source** job scans all Go, Node, and Python dependencies for known vulnerabilities. + + ```yaml + snyk test --all-projects --detection-depth=4 --sarif-file-output=snyk.sarif + ``` + + - Scans every module across `core/`, `framework/`, `transports/`, `plugins/`, `ui/`, and `tests/` + - Detection depth of 4 catches transitive dependencies + - Snyk CLI pinned to `v1.1303.2` + + + + + The **Snyk Code** job performs Static Application Security Testing on the full codebase. + A complete build is performed first (Go + Node + Python) so Snyk can analyze compiled artifacts. + + ```yaml + snyk code test --sarif-file-output=snyk-code.sarif + ``` + + - Detects injection flaws, hardcoded secrets, insecure crypto, and other code-level vulnerabilities + - Builds the full project before scanning for accurate analysis + + + + + Snyk checks can be skipped by including `--skip-pipeline` in the first line of a commit message. This is + intended for documentation-only or CI configuration changes. + + +--- + +## Container Image Security + +### Dockerfile Hardening + +Production containers follow a strict hardening checklist: + + + + Three stages — UI builder (Node), Go builder, and minimal Alpine runtime — ensure no build tools or + source code leak into the final image. + + + Production images use a FIPS 140-2 validated Alpine base image with compliant OpenSSL. + + + The FIPS base image includes a dedicated `appuser`. The container runs as this unprivileged user — + never as root. + + + Go binaries are compiled with `-ldflags="-w -s"` to strip debug symbols and DWARF information, + reducing attack surface and image size. + + + +Additional hardening measures: + +- **Static builds** — Compiled with `-tags "sqlite_static"` and `-extldflags '-static'` for fully static linking +- **Build verification** — `RUN test -f /app/main || exit 1` ensures the binary exists before proceeding +- **CVE patching** — Enterprise Dockerfiles include targeted patches (e.g., `apk upgrade --no-cache openssl` for CVE-2026-22796) +- **Minimal runtime dependencies** — The FIPS base image provides only essential libraries (`musl`, `libgcc`, `ca-certificates`) + +```dockerfile +# Runtime stage excerpt (production) +FROM +WORKDIR /app + +COPY --from=builder /app/main . +COPY --from=builder /app/docker-entrypoint.sh . + +RUN mkdir -p $APP_DIR/logs +USER appuser + +ENTRYPOINT ["/app/docker-entrypoint.sh"] +CMD ["/app/main"] +``` + + + FIPS-compliant hardened base image in Dockerfile + + +### Docker Scout + +[Docker Scout](https://docs.docker.com/scout/) is enabled at the Docker Hub repository level for the +`maximhq/bifrost` image. Every image pushed to Docker Hub is automatically scanned for CVEs against +continuously updated vulnerability databases. + + + Docker Scout image score showing vulnerability assessment + + +### GCP Artifact Registry Scanning + +Enterprise images are pushed to **GCP Artifact Registry** (and AWS ECR for select environments). +GCP Artifact Registry provides [built-in vulnerability scanning](https://cloud.google.com/artifact-registry/docs/analysis) +that automatically analyzes container images for OS and language package vulnerabilities. + +--- + +## Supply Chain Security + +### GitHub Actions SHA Pinning + +All GitHub Actions in the open-source repository are pinned to exact commit SHAs — not mutable version +tags. This prevents supply chain attacks where a compromised action maintainer could push malicious code +to an existing tag. + +```yaml +# Every action is pinned to a full SHA with a version comment +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 +- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 +``` + +**Coverage:** + +| Repository | Pinning Strategy | Actions Pinned | +| --- | --- | --- | +| `bifrost` (OSS) | Full SHA with version comment | 103 / 103 (100 %) | +| `bifrost-enterprise` | Version tags (`@v4`) | Upgrade planned | + +### NPM Provenance + +Published npm packages include [SLSA provenance attestations](https://docs.npmjs.com/generating-provenance-statements), +providing a verifiable link between the published package and its source commit. + +```yaml +permissions: + id-token: write # Required for npm provenance + +# ... +npm publish --provenance --access public +``` + +### Dependency Pinning in CI + +All language runtimes are pinned to specific versions across CI workflows to ensure reproducible builds +and prevent unexpected behavior from runtime updates. + +| Runtime | Pinned Version | Used For | +| --- | --- | --- | +| Go | `1.26.1` | Core build, tests | +| Node | `25` | UI build, npm packages | +| Python | `3.11` | Integration and governance tests | +| uv | SHA-pinned via `astral-sh/setup-uv` | Python package management | + +Python test dependencies are locked via `uv.lock` files for deterministic, reproducible installs: + +``` +tests/integrations/uv.lock +tests/governance/uv.lock +``` + + + Pinned dependency versions across CI workflows + + +--- + +## Dependency Management — Dependabot + +Dependabot monitors four ecosystems on a weekly schedule, automatically opening pull requests for +outdated or vulnerable dependencies. + + + + Covers `/core`, `/framework`, `/transports`, `/plugins/*`, and `/examples/**` + + + Covers `/ui`, `/npx`, and `/examples/**` + + + Monitors base images in `/transports` + + + Tracks action version updates across all workflows + + + +A separate **Dependabot Alerts** workflow runs daily and automatically +creates GitHub issues for any open Dependabot security alerts, categorized by severity and ecosystem. + +--- + +## Code Analysis — CodeQL + +GitHub's [CodeQL](https://codeql.github.com/) performs semantic code analysis on every push and pull request, +complementing Snyk's SAST coverage with GitHub-native findings. + +- Analyzes Go and JavaScript/TypeScript codebases +- Detects security vulnerabilities, bugs, and code quality issues using GitHub's query suites +- Results appear directly in the GitHub Security tab alongside Snyk findings + + + CodeQL analysis results in GitHub Security tab + + +--- + +## Workflow & Network Security + +### Principle of Least Privilege + +All GitHub Actions workflows follow the principle of least privilege. Permissions are set at the +job level, not the workflow level, and are scoped to the minimum required. + +| Permission | Granted To | Reason | +| --- | --- | --- | +| `contents: read` | All jobs (default) | Read repository code | +| `contents: write` | Tag creation, releases | Create git tags and releases | +| `security-events: write` | Snyk jobs | Upload SARIF to Security tab | +| `id-token: write` | Cloud auth, npm publish | OIDC federation, npm provenance | +| `pull-requests: write` | PR test reporters | Post test results as PR comments | + +### Tailscale VPN + +Enterprise deployment workflows authenticate through [Tailscale](https://tailscale.com/) before +accessing any infrastructure. This ensures that CI/CD runners can only reach deployment targets +through an encrypted, identity-aware network — never over the public internet. + +```yaml +- name: Authenticate Tailscale + uses: tailscale/github-action@v4 + with: + oauth-client-id: '${{ secrets.TS_OAUTH_CLIENT_ID }}' + oauth-secret: '${{ secrets.TS_OAUTH_SECRET }}' + tags: 'tag:gha-ci' + version: 1.84.0 +``` + +- **OAuth-based authentication** — No long-lived API keys; runners authenticate via OAuth client credentials +- **Version pinned** — Tailscale `1.84.0` to prevent unexpected behavior from updates +- **Tagged runners** — `gha-ci` tag enables Tailscale ACL policies scoped to CI/CD access + +### StepSecurity + +[StepSecurity](https://www.stepsecurity.io/) automatically hardens GitHub Actions workflows by applying +security best practices across all CI/CD pipelines. + +- Adds `permissions` blocks to workflows that are missing them +- Pins action versions to full SHAs where mutable tags were used +- Detects insecure patterns like unquoted interpolations and artifact poisoning risks + + + StepSecurity automated security hardening applied to GitHub Actions workflows + + +### CODEOWNERS + +Critical paths in the repository are protected by a `CODEOWNERS` file, ensuring that changes to +security-sensitive areas require review from designated maintainers before merging. + + + CODEOWNERS file enforcing review gates on security-critical paths + diff --git a/transports/Dockerfile b/transports/Dockerfile index a18bf4ff71..afc2357b7a 100644 --- a/transports/Dockerfile +++ b/transports/Dockerfile @@ -44,19 +44,14 @@ RUN go build \ -o /app/main \ ./bifrost-http -# Verify build succeeded +# Verify build succeeded and prepare entrypoint RUN test -f /app/main || (echo "Build failed" && exit 1) +RUN chmod +x /build/transports/docker-entrypoint.sh # --- Runtime Stage: Minimal runtime image --- -FROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 +FROM bifrosthq/dhi-alpine-base:3.22-fips_bifrost-v27032026 WORKDIR /app -# Install runtime dependencies for CGO-enabled binary -# musl: C standard library (required for CGO binaries) -# libgcc: GCC runtime library -# ca-certificates: For HTTPS connections -RUN apk add --no-cache musl libgcc ca-certificates wget - # Create data directory and set up user COPY --from=builder /app/main . COPY --from=builder /app/docker-entrypoint.sh . @@ -86,10 +81,7 @@ ENV GOGC="" \ GOMEMLIMIT="" -RUN mkdir -p $APP_DIR/logs && \ - adduser -D -s /bin/sh appuser && \ - chown -R appuser:appuser /app && \ - chmod +x /app/docker-entrypoint.sh +RUN mkdir -p $APP_DIR/logs USER appuser @@ -97,10 +89,6 @@ USER appuser VOLUME ["/app/data"] EXPOSE $APP_PORT -# Health check for container status monitoring -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD wget --no-verbose --tries=1 -O /dev/null http://127.0.0.1:${APP_PORT}/health || exit 1 - # Use entrypoint script that handles volume permissions and argument processing ENTRYPOINT ["/app/docker-entrypoint.sh"] CMD ["/app/main"] diff --git a/transports/Dockerfile.local b/transports/Dockerfile.local index 124e507d68..2a03e34e4d 100644 --- a/transports/Dockerfile.local +++ b/transports/Dockerfile.local @@ -64,19 +64,14 @@ RUN cd /build/transports && \ -o /app/main \ ./bifrost-http -# Verify build succeeded +# Verify build succeeded and prepare entrypoint RUN test -f /app/main || (echo "Build failed" && exit 1) +RUN chmod +x /build/transports/docker-entrypoint.sh # --- Runtime Stage: Minimal runtime image --- -FROM alpine:3.23.3 +FROM bifrosthq/dhi-alpine-base:3.22-fips_bifrost-v27032026 WORKDIR /app -# Install runtime dependencies for CGO-enabled binary -# musl: C standard library (required for CGO binaries) -# libgcc: GCC runtime library -# ca-certificates: For HTTPS connections -RUN apk add --no-cache musl libgcc ca-certificates wget - # Create data directory and set up user COPY --from=builder /app/main . COPY --from=builder /build/transports/docker-entrypoint.sh . @@ -96,10 +91,7 @@ ENV APP_PORT=$ARG_APP_PORT \ APP_DIR=$ARG_APP_DIR -RUN mkdir -p $APP_DIR/logs && \ - adduser -D -s /bin/sh appuser && \ - chown -R appuser:appuser /app && \ - chmod +x /app/docker-entrypoint.sh +RUN mkdir -p $APP_DIR/logs USER appuser @@ -107,10 +99,6 @@ USER appuser VOLUME ["/app/data"] EXPOSE $APP_PORT -# Health check for container status monitoring -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD wget --no-verbose --tries=1 -O /dev/null http://127.0.0.1:${APP_PORT}/health || exit 1 - # Use entrypoint script that handles volume permissions and argument processing ENTRYPOINT ["/app/docker-entrypoint.sh"] CMD ["/app/main"]