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
72 changes: 72 additions & 0 deletions .github/actions/setup-socket-firewall/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Setup Socket Firewall wrapper

Composite GitHub Action that installs Socket Firewall Enterprise (`sfw`) in
wrapper mode and routes supported package-manager commands through it for later
bash steps in the same job.

## Usage

```yaml
- name: Setup Socket Firewall wrapper
uses: ./.github/actions/setup-socket-firewall
with:
socket-api-key: ${{ secrets.SOCKET_API_KEY }}
```

Call the action after checkout and before the first `uv sync`, `uv lock`,
`uv pip`, or `pip install` step.

The action:

- downloads the Linux `sfw` binary when needed
- exports `SOCKET_API_KEY` and `SFW_TELEMETRY_DISABLED=true`
- configures `SFW_CUSTOM_REGISTRIES` with wrap hosts (including
`files.pythonhosted.org` for PyPI artifact downloads)
- writes a `BASH_ENV` file with package-manager wrapper functions

Wrapper functions apply only to later bash steps that source `BASH_ENV`. They
do not affect third-party Actions or dependency installs inside Docker builds
(those use BuildKit secrets — see repository Dockerfiles).

## Fork PRs / missing secret

Graphiti is a public repository. Fork pull requests do not receive repository
secrets. When `socket-api-key` is empty, this action **soft-skips**: it prints a
notice, sets `SOCKET_FIREWALL_ENABLED=false`, and leaves package managers
unwrapped so CI still succeeds. Same-repo runs with `SOCKET_API_KEY` configured
get full enforcement.

## API key scopes

`SOCKET_API_KEY` should include the Socket scopes required for Enterprise
wrapper mode (`packages` and `entitlements:list`). Configure it as a repository
Actions secret and in the `development` / `release` environments used by CI.

## Docker builds

Official release workflows pass `socket_api_key` as a BuildKit secret so image
dependency fetches go through `sfw`. They also set the non-secret
`SOCKET_FIREWALL_ENABLED=true` build argument, which separates enforced release
layers from public fallback layers in the BuildKit cache. In enforced mode, a
missing or empty `socket_api_key` fails the build. Dockerfiles default the
argument to `false` and install directly so community `docker build` / Compose
usage continues to work without a Socket key.

### Cache bypass on enforced builds

`sfw` inspects network requests, so anything served from cache is never
scanned. Two caches would otherwise hide dependencies from an enforced release
build:

- the **BuildKit layer cache**, which skips the install step entirely when its
inputs are unchanged
- the **`uv` cache mount**, which lets `uv` resolve wheels from disk without
contacting PyPI even when the step does run

Enforced builds defeat both. Release workflows pass
`SOCKET_SCAN_ID=<run_id>-<run_attempt>`, which is unique per run and invalidates
the install layer, and the enforced branch exports `UV_NO_CACHE=1`. Without
these, an image whose dependencies had not changed could ship a package that
Socket flagged after the layer was first built. Community builds leave
`SOCKET_SCAN_ID` empty and keep both caches, so unauthenticated builds stay
fast.
133 changes: 133 additions & 0 deletions .github/actions/setup-socket-firewall/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
name: Setup Socket Firewall wrapper
description: >
Installs Socket Firewall Enterprise wrapper mode and routes supported package
manager commands through sfw for subsequent bash steps. Soft-skips when no
API key is provided (fork PRs / public contributors).

inputs:
socket-api-key:
description: >
Socket API key with packages and entitlements:list scopes. When empty
(typical for fork PRs), the action prints a notice and leaves package
managers unwrapped so the job still succeeds.
required: false
default: ''
sfw-download-url:
description: Download URL for the Linux sfw binary used by CI runners.
required: false
default: https://github.com/SocketDev/firewall-release/releases/latest/download/sfw-linux-x86_64

runs:
using: composite
steps:
- name: Install sfw
shell: bash
env:
SFW_DOWNLOAD_URL: ${{ inputs['sfw-download-url'] }}
SOCKET_API_KEY_INPUT: ${{ inputs['socket-api-key'] }}
run: |
set -euo pipefail

if [ -z "$SOCKET_API_KEY_INPUT" ]; then
echo "SOCKET_FIREWALL_ENABLED=false" >> "$GITHUB_ENV"
echo "::notice::Socket Firewall skipped: socket-api-key is empty (fork PR or missing secret)."
exit 0
fi

echo "SOCKET_FIREWALL_ENABLED=true" >> "$GITHUB_ENV"

if ! command -v sfw >/dev/null 2>&1; then
mkdir -p "$RUNNER_TEMP/socket-firewall"
curl -fsSL "$SFW_DOWNLOAD_URL" -o "$RUNNER_TEMP/socket-firewall/sfw"
chmod +x "$RUNNER_TEMP/socket-firewall/sfw"
echo "$RUNNER_TEMP/socket-firewall" >> "$GITHUB_PATH"
fi

- name: Export Socket Firewall environment
shell: bash
env:
SOCKET_API_KEY_INPUT: ${{ inputs['socket-api-key'] }}
run: |
set -euo pipefail

if [ -z "$SOCKET_API_KEY_INPUT" ]; then
exit 0
fi

{
echo "SOCKET_API_KEY<<EOF"
echo "$SOCKET_API_KEY_INPUT"
echo "EOF"
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
echo "SFW_TELEMETRY_DISABLED=true"
} >> "$GITHUB_ENV"
# Auxiliary hosts package managers contact that sfw doesn't treat as
# registries by default (wrap = TLS-terminated passthrough, no package
# inspection). Keep in sync with zep-proprietary setup-socket-firewall.
# storage.googleapis.com - Go module zips served via proxy.golang.org
# classic.yarnpkg.com - Yarn classic version-check / metadata host
# files.pythonhosted.org - Python package files served by PyPI
extra="wrap:storage.googleapis.com,wrap:classic.yarnpkg.com,wrap:files.pythonhosted.org"
if [ -n "${SFW_CUSTOM_REGISTRIES:-}" ]; then
echo "SFW_CUSTOM_REGISTRIES=${SFW_CUSTOM_REGISTRIES},${extra}" >> "$GITHUB_ENV"
else
echo "SFW_CUSTOM_REGISTRIES=${extra}" >> "$GITHUB_ENV"
fi

- name: Configure bash wrapper functions
shell: bash
env:
SOCKET_API_KEY_INPUT: ${{ inputs['socket-api-key'] }}
run: |
set -euo pipefail

if [ -z "$SOCKET_API_KEY_INPUT" ]; then
exit 0
fi

mkdir -p "$RUNNER_TEMP/socket-firewall"
wrapper_file="$RUNNER_TEMP/socket-firewall/bash_env"
cat > "$wrapper_file" <<'EOF'
if command -v sfw >/dev/null 2>&1; then
npm() {
case "${1:-}" in
ci|install|i|add|update|view|pack) command sfw npm "$@" ;;
*) command npm "$@" ;;
esac
}
yarn() {
case "${1:-}" in
add|import|install|remove|upgrade|upgrade-interactive) command sfw yarn "$@" ;;
*) command yarn "$@" ;;
esac
}
pnpm() {
case "${1:-}" in
add|fetch|install|remove|update) command sfw pnpm "$@" ;;
*) command pnpm "$@" ;;
esac
}
pip() { command sfw pip "$@"; }
pip3() { command sfw pip3 "$@"; }
uv() {
case "${1:-}" in
add|build|export|lock|pip|remove|sync|tool) command sfw uv "$@" ;;
*) command uv "$@" ;;
esac
}
cargo() { command sfw cargo "$@"; }
go() {
case "${1:-}:${2:-}" in
get:*|install:*|mod:download|mod:tidy|work:sync) command sfw go "$@" ;;
*) command go "$@" ;;
esac
}
mvn() { command sfw mvn "$@"; }
gradle() { command sfw gradle "$@"; }
gem() { command sfw gem "$@"; }
bundle() { command sfw bundle "$@"; }
dotnet() { command sfw dotnet "$@"; }
fi
EOF

echo "BASH_ENV=$wrapper_file" >> "$GITHUB_ENV"
4 changes: 4 additions & 0 deletions .github/workflows/mcp-server-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Socket Firewall wrapper
uses: ./.github/actions/setup-socket-firewall
with:
socket-api-key: ${{ secrets.SOCKET_API_KEY }}
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/release-graphiti-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ jobs:
url: https://pypi.org/p/zep-cloud
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Socket Firewall wrapper
uses: ./.github/actions/setup-socket-firewall
with:
socket-api-key: ${{ secrets.SOCKET_API_KEY }}
- name: Set up Python 3.11
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
Expand All @@ -23,6 +27,10 @@ jobs:
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
with:
version: "latest"
# Resolve the project environment explicitly through the sfw wrapper.
# `uv run` below then uses the already-synced environment.
- name: Install dependencies
run: uv sync --frozen
- name: Compare pyproject version with tag
run: |
TAG_VERSION=${GITHUB_REF#refs/tags/}
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/release-mcp-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,16 @@ jobs:
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
build-args: |
SOCKET_FIREWALL_ENABLED=true
SOCKET_SCAN_ID=${{ github.run_id }}-${{ github.run_attempt }}
MCP_SERVER_VERSION=${{ steps.version.outputs.version }}
GRAPHITI_CORE_VERSION=${{ steps.graphiti.outputs.graphiti_version }}
BUILD_DATE=${{ steps.meta.outputs.build_date }}
VCS_REF=${{ steps.version.outputs.version }}
# Socket Firewall API key as a BuildKit secret (not a build-arg/ENV)
# so it is never baked into an image layer.
secrets: |
socket_api_key=${{ secrets.SOCKET_API_KEY }}

- name: Create release summary
run: |
Expand Down
13 changes: 7 additions & 6 deletions .github/workflows/release-server-container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@ jobs:
with:
python-version: "3.11"

- name: Install uv
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
with:
version: "latest"

- name: Extract version
id: version
run: |
Expand All @@ -54,7 +49,7 @@ jobs:

if [ -z "$VERSION" ]; then
# Fallback: check pyproject.toml version
VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
echo "Version from pyproject.toml: $VERSION"
else
echo "Version from git tag: $VERSION"
Expand Down Expand Up @@ -144,9 +139,15 @@ jobs:
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
SOCKET_FIREWALL_ENABLED=true
SOCKET_SCAN_ID=${{ github.run_id }}-${{ github.run_attempt }}
GRAPHITI_VERSION=${{ steps.version.outputs.version }}
BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
VCS_REF=${{ github.sha }}
# Socket Firewall API key as a BuildKit secret (not a build-arg/ENV)
# so it is never baked into an image layer.
secrets: |
socket_api_key=${{ secrets.SOCKET_API_KEY }}

- name: Summary
if: steps.version.outputs.skip != 'true'
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/server-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Socket Firewall wrapper
uses: ./.github/actions/setup-socket-firewall
with:
socket-api-key: ${{ secrets.SOCKET_API_KEY }}
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
Expand Down
Loading
Loading