Skip to content
Merged
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
5 changes: 3 additions & 2 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ docker compose -f docker-compose.sentinelguard.yml up --build

# Start OpenAI-compatible LLM gateway from generated config
export OPENAI_API_KEY="sk-..."
export SENTINELGUARD_GATEWAY_API_KEY="replace-with-a-random-local-token"
export SENTINELGUARD_GATEWAY_API_KEY="$(sentinelguard token)"
sentinelguard gateway \
--config sentinelguard.yaml \
--gateway-config sentinelguard-gateway.yaml \
Expand All @@ -141,8 +141,9 @@ sentinelguard gateway --provider kimi --port 8080

`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, and
`MOONSHOT_API_KEY` are upstream provider keys. `SENTINELGUARD_GATEWAY_API_KEY`
is a client-facing token you choose for apps and IDEs that call the
is a client-facing token you generate for apps and IDEs that call the
SentinelGuard gateway.
Package-mode library usage does not need this gateway token.

Point OpenAI-compatible apps or IDEs to:

Expand Down
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ response, and returns the safe response.
pip install "sentinelguard[gateway,monitoring]"

export OPENAI_API_KEY="sk-..."
export SENTINELGUARD_GATEWAY_API_KEY="replace-with-a-random-local-token"
export SENTINELGUARD_GATEWAY_API_KEY="$(sentinelguard token)"
sentinelguard init
sentinelguard gateway \
--config sentinelguard.yaml \
Expand All @@ -205,8 +205,10 @@ sentinelguard gateway \
```

`OPENAI_API_KEY` is the upstream provider key. `SENTINELGUARD_GATEWAY_API_KEY`
is a client-facing token that you choose; apps, SDKs, and IDEs use it when they
call SentinelGuard at `http://localhost:8080/v1`.
is a client-facing token generated by you for apps, SDKs, and IDEs that call
SentinelGuard at `http://localhost:8080/v1`.
Package mode does not need this gateway token. Gateway mode uses it to protect
the proxy endpoint and keep real provider keys on the gateway process.

For a quick single-provider run without generated files:

Expand All @@ -217,6 +219,10 @@ sentinelguard gateway --provider openai --port 8080
Manage scanner and gateway YAML from the CLI:

```bash
# Local gateway tokens
sentinelguard token
sentinelguard token --env

# Scanner policy
sentinelguard config set prompt_scanners.pii.threshold 0.3 --file sentinelguard.yaml
sentinelguard config disable toxicity --type prompt --file sentinelguard.yaml
Expand All @@ -234,19 +240,16 @@ docker build -t sentinelguard-gateway .

docker run --rm -p 8080:8080 \
-e OPENAI_API_KEY="$OPENAI_API_KEY" \
-e SENTINELGUARD_GATEWAY_API_KEY="replace-with-a-random-local-token" \
-e SENTINELGUARD_GATEWAY_API_KEY="$(sentinelguard token)" \
sentinelguard-gateway \
gateway --provider openai --client-api-key-env SENTINELGUARD_GATEWAY_API_KEY
```

With Docker Compose:

```bash
sentinelguard init
cp .env.example .env
# Edit .env and set at least one upstream provider key.
export OPENAI_API_KEY="sk-..."
export SENTINELGUARD_GATEWAY_API_KEY="replace-with-a-random-local-token"
sentinelguard init --with-env
# Edit .env and set at least one upstream provider key, such as OPENAI_API_KEY.
docker compose -f docker-compose.sentinelguard.yml up --build
```

Expand Down Expand Up @@ -324,7 +327,7 @@ Then point an OpenAI-compatible client at the gateway:
from openai import OpenAI

client = OpenAI(
api_key="replace-with-a-random-local-token",
api_key="your-sentinelguard-gateway-token",
base_url="http://localhost:8080/v1",
)

Expand All @@ -340,7 +343,7 @@ client-facing base URL and client-facing API key:
```bash
# In the app container or app runtime:
export OPENAI_BASE_URL="http://localhost:8080/v1"
export OPENAI_API_KEY="replace-with-a-random-local-token"
export OPENAI_API_KEY="$SENTINELGUARD_GATEWAY_API_KEY"
```

Keep the real upstream provider key on the SentinelGuard gateway process or
Expand Down Expand Up @@ -660,7 +663,7 @@ Gateway audit logs can be enabled for incident tracking without storing chat
content:

```bash
export SENTINELGUARD_AUDIT_SALT="use-a-long-random-secret"
export SENTINELGUARD_AUDIT_SALT="$(sentinelguard token --prefix sgaudit)"
```

Audit events are emitted as JSON through the `sentinelguard.audit` logger when
Expand Down
15 changes: 9 additions & 6 deletions docs/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ Application or IDE
The example below uses two different keys:

- `OPENAI_API_KEY` is the upstream provider key used by SentinelGuard to call OpenAI.
- `SENTINELGUARD_GATEWAY_API_KEY` is a client-facing token that you choose. Your
- `SENTINELGUARD_GATEWAY_API_KEY` is a client-facing token that you generate. Your
apps, IDEs, and SDKs use this token when calling SentinelGuard at
`http://localhost:8080/v1`.

```bash
pip install "sentinelguard[gateway,monitoring]"
export OPENAI_API_KEY="sk-..."
export SENTINELGUARD_GATEWAY_API_KEY="replace-with-a-random-local-token"
export SENTINELGUARD_GATEWAY_API_KEY="$(sentinelguard token)"

sentinelguard init
sentinelguard gateway \
Expand All @@ -31,8 +31,11 @@ sentinelguard gateway \
--port 8080
```

For local testing, the gateway token can be any random string. In shared or
production deployments, use a generated secret and keep it out of source code.
For local testing, `sentinelguard token` generates a secure random local token.
In shared or production deployments, keep that value out of source code.
If you remove `client_api_key_env` and all `virtual_keys` from the gateway YAML,
client-token authentication is disabled; keep it enabled for shared Docker,
Kubernetes, or team gateways.

## Supported Providers

Expand Down Expand Up @@ -109,8 +112,8 @@ curl http://localhost:8080/gateway/v1/provider-health
## Docker Compose

```bash
sentinelguard init
cp .env.example .env
sentinelguard init --with-env
# Edit .env and set at least one upstream provider key, such as OPENAI_API_KEY.
docker compose -f docker-compose.sentinelguard.yml up --build
```

Expand Down
10 changes: 7 additions & 3 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,19 @@ Gateway mode usually has two kinds of keys:
- An upstream provider key, such as `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,
`GEMINI_API_KEY`, or `MOONSHOT_API_KEY`. SentinelGuard uses this to call the
model provider.
- `SENTINELGUARD_GATEWAY_API_KEY`, a client-facing token that you choose.
- `SENTINELGUARD_GATEWAY_API_KEY`, a client-facing token that you generate.
Applications and IDEs use this token when they call SentinelGuard at
`http://localhost:8080/v1`.

For local testing, this gateway token can be any random string:
For local testing, generate the gateway token locally:

```bash
export SENTINELGUARD_GATEWAY_API_KEY="replace-with-a-random-local-token"
export SENTINELGUARD_GATEWAY_API_KEY="$(sentinelguard token)"
```

Package-mode library usage does not need `SENTINELGUARD_GATEWAY_API_KEY`.
Gateway mode uses it to protect the local or shared proxy endpoint.

Supported gateway providers include OpenAI, Anthropic Claude, Google Gemini,
Kimi / Moonshot, DeepSeek, Mistral, MiniMax, Ollama, Hugging Face, and custom
OpenAI-compatible providers such as vLLM, TGI, llama.cpp, or private model
Expand All @@ -87,6 +90,7 @@ gateways.
Scanner settings can be updated without opening the YAML file:

```bash
sentinelguard token --env
sentinelguard config init --preset standard --output sentinelguard.yaml
sentinelguard config set prompt_scanners.pii.threshold 0.3 --file sentinelguard.yaml
sentinelguard config disable toxicity --type prompt --file sentinelguard.yaml
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ description: Security-first LLM gateway and guardrails framework for AI applicat
<p class="sg-panel-title">Install and start the gateway</p>
<pre><code>pip install "sentinelguard[gateway,monitoring]"
export OPENAI_API_KEY="sk-..."
export SENTINELGUARD_GATEWAY_API_KEY="your-gateway-token"
export SENTINELGUARD_GATEWAY_API_KEY="$(sentinelguard token)"
sentinelguard init
sentinelguard gateway \
--config sentinelguard.yaml \
Expand Down
2 changes: 2 additions & 0 deletions examples/helm/sentinelguard/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ env:
HF_TOKEN: ""
HUGGINGFACE_API_KEY: ""
OLLAMA_API_KEY: ""
# Generate with: sentinelguard token
SENTINELGUARD_GATEWAY_API_KEY: ""
# Generate with: sentinelguard token --prefix sgaudit
SENTINELGUARD_AUDIT_SALT: ""

gatewayConfig: |
Expand Down
4 changes: 2 additions & 2 deletions examples/kubernetes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ Create the gateway Secret:
kubectl create secret generic sentinelguard-gateway-secrets \
-n sentinelguard \
--from-literal=OPENAI_API_KEY="$OPENAI_API_KEY" \
--from-literal=SENTINELGUARD_GATEWAY_API_KEY="replace-with-shared-gateway-token" \
--from-literal=SENTINELGUARD_AUDIT_SALT="$(openssl rand -hex 32)"
--from-literal=SENTINELGUARD_GATEWAY_API_KEY="$(sentinelguard token)" \
--from-literal=SENTINELGUARD_AUDIT_SALT="$(sentinelguard token --prefix sgaudit)"
```

For Anthropic, Gemini, Kimi/Moonshot, DeepSeek, Mistral, MiniMax, or Hugging
Expand Down
4 changes: 2 additions & 2 deletions examples/kubernetes/secret.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ stringData:
MISTRAL_API_KEY: ""
MINIMAX_API_KEY: ""
HF_TOKEN: ""
SENTINELGUARD_GATEWAY_API_KEY: "replace-with-gateway-client-token"
SENTINELGUARD_AUDIT_SALT: "replace-with-random-audit-salt"
SENTINELGUARD_GATEWAY_API_KEY: "generate-with-sentinelguard-token"
SENTINELGUARD_AUDIT_SALT: "generate-with-sentinelguard-token-prefix-sgaudit"
3 changes: 2 additions & 1 deletion examples/terraform/kubernetes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ This example deploys the SentinelGuard Helm chart with Terraform.
terraform init
terraform apply \
-var='openai_api_key=sk-...' \
-var='gateway_api_key=replace-with-a-random-local-token'
-var="gateway_api_key=$(sentinelguard token)" \
-var="audit_salt=$(sentinelguard token --prefix sgaudit)"
```

The example assumes your local Kubernetes context already points at the target
Expand Down
1 change: 0 additions & 1 deletion examples/terraform/kubernetes/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,4 @@ variable "audit_salt" {
description = "Salt used for privacy-safe audit hashing."
type = string
sensitive = true
default = "change-me"
}
4 changes: 2 additions & 2 deletions examples/test_apps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Or run the same gateway as a Docker proxy:

```bash
export OPENAI_API_KEY="sk-..."
export SENTINELGUARD_GATEWAY_API_KEY="replace-with-a-random-local-token"
export SENTINELGUARD_GATEWAY_API_KEY="$(sentinelguard token)"
docker compose up --build
```

Expand Down Expand Up @@ -233,7 +233,7 @@ base URL and app-facing API key to the gateway:

```bash
export OPENAI_BASE_URL="http://localhost:8080/v1"
export OPENAI_API_KEY="replace-with-a-random-local-token"
export OPENAI_API_KEY="$SENTINELGUARD_GATEWAY_API_KEY"
```

The gateway container keeps the real upstream provider API key. The app only
Expand Down
64 changes: 61 additions & 3 deletions sentinelguard/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
sentinelguard init
sentinelguard serve --port 8000
sentinelguard gateway --provider openai --port 8080
sentinelguard token --env
sentinelguard gateway --provider anthropic --port 8080
sentinelguard gateway --provider gemini --port 8080
sentinelguard config show
Expand Down Expand Up @@ -69,6 +70,11 @@ def main(argv: Optional[List[str]] = None) -> int:
action="store_true",
help="Do not create docker-compose.sentinelguard.yml",
)
init_parser.add_argument(
"--with-env",
action="store_true",
help="Create a local .env file with generated SentinelGuard gateway tokens",
)
init_parser.add_argument(
"--force",
action="store_true",
Expand Down Expand Up @@ -127,6 +133,33 @@ def main(argv: Optional[List[str]] = None) -> int:
)
gateway_parser.add_argument("--reload", action="store_true", help="Enable auto-reload")

# ── token command ──
token_parser = subparsers.add_parser(
"token",
help="Generate a local SentinelGuard gateway client token",
)
token_parser.add_argument(
"--env",
action="store_true",
help="Print a shell export command instead of only the token",
)
token_parser.add_argument(
"--name",
default="SENTINELGUARD_GATEWAY_API_KEY",
help="Environment variable name used with --env",
)
token_parser.add_argument(
"--prefix",
default="sgw",
help="Token prefix, such as sgw or sgaudit",
)
token_parser.add_argument(
"--bytes",
type=int,
default=32,
help="Number of random bytes to use; minimum is 16",
)

# ── config command ──
config_parser = subparsers.add_parser("config", help="Manage configuration")
config_sub = config_parser.add_subparsers(dest="config_action")
Expand Down Expand Up @@ -215,6 +248,8 @@ def main(argv: Optional[List[str]] = None) -> int:
return _handle_serve(args)
elif args.command == "gateway":
return _handle_gateway(args)
elif args.command == "token":
return _handle_token(args)
elif args.command == "config":
return _handle_config(args)
elif args.command == "gateway-config":
Expand All @@ -237,6 +272,7 @@ def _handle_init(args: argparse.Namespace) -> int:
preset=args.preset,
docker_image=args.docker_image,
include_docker=not args.without_docker,
include_env=args.with_env,
force=args.force,
)
except ValueError as exc:
Expand All @@ -259,10 +295,16 @@ def _handle_init(args: argparse.Namespace) -> int:
if result.profile == "gateway":
print("\nNext steps:")
print(' 1. python -m pip install "sentinelguard[gateway,monitoring]"')
print(" 2. export SENTINELGUARD_GATEWAY_API_KEY=<gateway-token>")
print(" 3. export OPENAI_API_KEY=<provider-key>")
if args.with_env:
print(" 2. Add your provider key to .env, such as OPENAI_API_KEY")
start_step = 3
else:
print(' 2. export SENTINELGUARD_GATEWAY_API_KEY="$(sentinelguard token)"')
print(' 3. export SENTINELGUARD_AUDIT_SALT="$(sentinelguard token --prefix sgaudit)"')
print(" 4. export OPENAI_API_KEY=<provider-key>")
start_step = 5
print(
" 4. sentinelguard gateway "
f" {start_step}. sentinelguard gateway "
"--config sentinelguard.yaml "
"--gateway-config sentinelguard-gateway.yaml "
"--port 8080"
Expand All @@ -278,6 +320,22 @@ def _handle_init(args: argparse.Namespace) -> int:
return 0


def _handle_token(args: argparse.Namespace) -> int:
"""Handle the token command."""
from sentinelguard.cli.bootstrap import generate_gateway_token

if args.bytes < 16:
print("Error: --bytes must be at least 16")
return 1

token = generate_gateway_token(prefix=args.prefix, nbytes=args.bytes)
if args.env:
print(f'export {args.name}="{token}"')
else:
print(token)
return 0


def _handle_scan(args: argparse.Namespace) -> int:
"""Handle the scan command."""
from sentinelguard import SentinelGuard, GuardConfig
Expand Down
Loading
Loading