Skip to content
Closed
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
d25caf1
feat: limit auth deps to bearer token and ip allowlist
Prajna1999 Jul 23, 2026
1ab6c17
Merge branch 'main' into feat/auth-authz-with-backend
Prajna1999 Aug 17, 2026
3c1f8a0
test cases
Prajna1999 Aug 17, 2026
6948d49
Merge remote-tracking branch 'refs/remotes/origin/feat/auth-authz-wit…
Prajna1999 Aug 17, 2026
8f1c610
trigger CI
Prajna1999 Aug 17, 2026
9d1a09f
triggger CI
Prajna1999 Aug 17, 2026
515df10
trigger CI
Prajna1999 Aug 17, 2026
1119647
pre-commit hook trigger
Prajna1999 Aug 17, 2026
240efae
chore: guardrails hub login issue
Prajna1999 Aug 25, 2026
9ea93df
feat: tracing(in DB) and logger
Prajna1999 Aug 26, 2026
576037f
feat: add sentry, otel wiring
Prajna1999 Aug 26, 2026
afc2992
Merge branch 'main' into feat/auth-authz-with-backend
Prajna1999 Sep 1, 2026
e7398dc
Merge branch 'main' into logging/traceability
Prajna1999 Sep 1, 2026
da8a567
PR fixes and nits
Prajna1999 Sep 1, 2026
b546611
Merge branch 'feat/auth-authz-with-backend' into logging/traceability
Prajna1999 Sep 1, 2026
2fd0036
fix: remove family refs and added hardening of logs
Prajna1999 Sep 1, 2026
64e23d9
Merge remote-tracking branch 'refs/remotes/origin/logging/traceabilit…
Prajna1999 Sep 1, 2026
463b41d
chore: remove .claude skills
Prajna1999 Sep 1, 2026
bdca1d9
comment string on parse_csv_list
Prajna1999 Sep 1, 2026
4ac04ed
chore: rename parse ip func
Prajna1999 Sep 1, 2026
cbe5cd8
Merge branch 'feat/auth-authz-with-backend' into logging/traceability
Prajna1999 Sep 1, 2026
21308ae
Merge branch 'main' into logging/sentry-otel
Prajna1999 Sep 2, 2026
e9bd942
Merge branch 'logging/traceability' into logging/sentry-otel
Prajna1999 Sep 2, 2026
686b940
sentry/telemetry and local validators update
Prajna1999 Sep 2, 2026
9a81451
chore: add otel enabled flag
Prajna1999 Sep 2, 2026
1cf2cef
logger and sentry logging cleanups
Prajna1999 Sep 3, 2026
8683193
change request_id to request_log_id
Prajna1999 Sep 4, 2026
0096cf8
chore: update gitignore
Prajna1999 Sep 4, 2026
7c6b65f
chore: update ci.yml, dockerfile etc removing guardrails hub download…
Prajna1999 Sep 6, 2026
553e5ef
feat: add more fields to input/output
Prajna1999 Sep 9, 2026
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
6 changes: 4 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres

SENTRY_DSN=
OTEL_ENABLED=false

# Configure these with your own Docker registry images

Expand All @@ -26,8 +27,9 @@ GUARDRAILS_HUB_API_KEY="<ADD-KEY>"
HF_TOKEN="<ADD-HF-KEY>"
# SHA-256 hex digest of your bearer token (64 lowercase hex chars)
AUTH_TOKEN="<ADD-HASH-TOKEN>"
KAAPI_AUTH_URL="<ADD-KAAPI-AUTH-URL>"
KAAPI_AUTH_TIMEOUT=5
# Comma-separated source IPs allowed to call this service (kaapi-backend).
# Empty disables the check; required when ENVIRONMENT=production.
ALLOWED_IPS="127.0.0.1"

# URL for the guardrails API — required for the multiple_validators evaluation script
GUARDRAILS_API_URL="http://localhost:8001/api/v1/guardrails/"
Expand Down
5 changes: 3 additions & 2 deletions .env.test.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ OPENAI_API_KEY="<ADD-KEY>"
GUARDRAILS_HUB_API_KEY="<ADD-KEY>"
# SHA-256 hex digest of your bearer token (64 lowercase hex chars)
AUTH_TOKEN="<ADD-HASH-TOKEN>"
KAAPI_AUTH_URL="<ADD-KAAPI-AUTH-URL>"
KAAPI_AUTH_TIMEOUT=5
# Comma-separated source IPs allowed to call this service (kaapi-backend).
# Empty disables the check; required when ENVIRONMENT=production.
ALLOWED_IPS="127.0.0.1"
10 changes: 0 additions & 10 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ jobs:
AUTH_TOKEN_HASH="$(echo -n "ci-test-token" | sha256sum | awk '{print $1}')"
fi
sed -i "s|AUTH_TOKEN=\"<ADD-HASH-TOKEN>\"|AUTH_TOKEN=\"${AUTH_TOKEN_HASH}\"|" .env.test
sed -i "s|GUARDRAILS_HUB_API_KEY=\"<ADD-KEY>\"|GUARDRAILS_HUB_API_KEY=\"${{ secrets.GUARDRAILS_HUB_API_KEY }}\"|" .env.test
cp .env.test .env

- name: Install uv
Expand All @@ -55,15 +54,6 @@ jobs:
run: uv sync
working-directory: backend

- name: Install Guardrails hub validators
env:
GUARDRAILS_HUB_API_KEY: ${{ secrets.GUARDRAILS_HUB_API_KEY }}
run: |
source .venv/bin/activate
chmod +x scripts/install_guardrails_from_hub.sh
scripts/install_guardrails_from_hub.sh
working-directory: backend

- name: Activate virtual environment and run Alembic migrations
run: |
source .venv/bin/activate
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ backend/app/evaluation/outputs/**/*.json

# marimo related artefacts
backend/notebooks/*.csv
**/__marimo__/
**/__marimo__/

backend/logs/app.log
10 changes: 5 additions & 5 deletions backend/.guardrails/hub_registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,39 @@
"exports": [
"BanList"
],
"installed_at": "2026-05-12T05:28:55.036165+00:00",
"installed_at": "2026-08-26T05:41:11.480494+00:00",
"package_name": "guardrails-grhub-ban-list"
},
"guardrails/llm_critic": {
"import_path": "guardrails_grhub_llm_critic",
"exports": [
"LLMCritic"
],
"installed_at": "2026-05-12T05:29:05.596003+00:00",
"installed_at": "2026-08-26T05:41:14.741349+00:00",
"package_name": "guardrails-grhub-llm-critic"
},
"guardrails/llamaguard_7b": {
"import_path": "guardrails_grhub_llamaguard_7b",
"exports": [
"LlamaGuard7B"
],
"installed_at": "2026-05-12T05:29:16.995441+00:00",
"installed_at": "2026-08-26T05:41:18.742264+00:00",
"package_name": "guardrails-grhub-llamaguard-7b"
},
"guardrails/profanity_free": {
"import_path": "guardrails_grhub_profanity_free",
"exports": [
"ProfanityFree"
],
"installed_at": "2026-05-12T05:29:28.079410+00:00",
"installed_at": "2026-08-26T05:41:26.279899+00:00",
"package_name": "guardrails-grhub-profanity-free"
},
"guardrails/nsfw_text": {
"import_path": "guardrails_grhub_nsfw_text",
"exports": [
"NSFWText"
],
"installed_at": "2026-05-12T05:29:50.654888+00:00",
"installed_at": "2026-08-26T05:41:36.879459+00:00",
"package_name": "guardrails-grhub-nsfw-text"
}
}
Expand Down
11 changes: 2 additions & 9 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,15 @@ ENV HF_HOME=/app/hf_cache
RUN --mount=type=secret,id=HF_TOKEN \
HF_TOKEN="$(cat /run/secrets/HF_TOKEN 2>/dev/null || true)" \
/app/.venv/bin/python -c "from transformers import AutoTokenizer, AutoModelForSequenceClassification; \
AutoTokenizer.from_pretrained('textdetox/xlmr-large-toxicity-classifier', cache_dir='/app/hf_cache'); \
AutoModelForSequenceClassification.from_pretrained('textdetox/xlmr-large-toxicity-classifier', cache_dir='/app/hf_cache')"
AutoTokenizer.from_pretrained('michellejieli/NSFW_text_classifier', cache_dir='/app/hf_cache'); \
AutoModelForSequenceClassification.from_pretrained('michellejieli/NSFW_text_classifier', cache_dir='/app/hf_cache')"

RUN --mount=type=secret,id=HF_TOKEN \
HF_TOKEN="$(cat /run/secrets/HF_TOKEN 2>/dev/null || true)" \
/app/.venv/bin/python -c "from transformers import AutoTokenizer, AutoModelForTokenClassification; \
AutoTokenizer.from_pretrained('dslim/bert-base-NER-uncased', cache_dir='/app/hf_cache'); \
AutoModelForTokenClassification.from_pretrained('dslim/bert-base-NER-uncased', cache_dir='/app/hf_cache')"

# Pre-install Guardrails hub validators so container startup is not blocked by downloads
RUN --mount=type=secret,id=GUARDRAILS_HUB_API_KEY \
GUARDRAILS_HUB_API_KEY="$(cat /run/secrets/GUARDRAILS_HUB_API_KEY 2>/dev/null || true)" \
ENABLE_METRICS="false" \
ENABLE_REMOTE_INFERENCING="true" \
/app/scripts/install_guardrails_from_hub.sh

# -------------------------------
# Entrypoint (runtime setup)
# -------------------------------
Expand Down
33 changes: 24 additions & 9 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,19 +191,32 @@ echo -n "your-plain-text-token" | shasum -a 256

Set the resulting digest as `AUTH_TOKEN` in your `.env` / `.env.test`.

## Multi-tenant API Key Configuration
## Caller Authentication

Ban List and LLM Prompt Config APIs use `X-API-KEY` auth instead of bearer token auth.
kaapi-guardrails is an internal service. Its only caller is kaapi-backend, which authenticates the
end user and resolves the tenant before calling.

Required environment variables:
- `KAAPI_AUTH_URL`: Base URL of the Kaapi auth service used to verify API keys.
- `KAAPI_AUTH_TIMEOUT`: Timeout in seconds for auth verification calls.
Every request (except the health check) must carry:

At runtime, the backend calls:
- `GET {KAAPI_AUTH_URL}/apikeys/verify`
- Header: `X-API-KEY: <token>`
- `Authorization: Bearer <plain-text-token>`
- `X-ORGANIZATION-ID: <int>` and `X-PROJECT-ID: <int>` — the tenant, resolved by kaapi-backend
- and originate from an IP listed in `ALLOWED_IPS`

If verification succeeds, tenant's scope (`organization_id`, `project_id`) is resolved from the auth response and applied to tenant-scoped CRUD operations (for example Ban Lists and LLM Prompt Configs).
`ALLOWED_IPS` is a comma-separated list of source IPs allowed to reach this service:

```bash
ALLOWED_IPS="10.0.3.14"
```

Leave it empty to disable the check for local development. It is required when
`ENVIRONMENT=production` — the service will not start without it.

Rejections:
- Source IP not whitelisted → `403` (checked before the token)
- Missing or invalid token → `401`
- Missing or non-integer tenant headers → `422`

Tenant scope is never read from the query string or request body.

## Guardrails AI Setup

Expand Down Expand Up @@ -339,3 +352,5 @@ class GenderAssumptionBiasSafetyValidatorConfig(BaseValidatorConfig):
```

3. In `backend/app/schemas/guardrail_config.py`, add the newly created config class to `ValidatorConfigItem`.

# test
84 changes: 84 additions & 0 deletions backend/app/alembic/versions/010_add_traceability_columns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""Add traceability columns to request_log and validator_log

Revision ID: 010
Revises: 009
Create Date: 2026-08-25 00:00:00.000000

"""

from typing import Sequence, Union

import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects.postgresql import JSONB

revision: str = "010"
down_revision = "009"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.add_column(
"request_log",
sa.Column(
"metadata",
JSONB,
nullable=True,
comment="Full run_guardrails request payload",
),
)
op.add_column(
"validator_log",
sa.Column(
"order",
sa.Integer(),
nullable=True,
comment="1-based execution order of the validator within the request",
),
)
op.add_column(
"validator_log",
sa.Column(
"duration_ms",
sa.Integer(),
nullable=True,
comment="Wall-clock execution time of the validator in milliseconds",
),
)
op.add_column(
"validator_log",
sa.Column(
"stage",
sa.String(),
nullable=True,
comment="Stage the validator checked (input or output)",
),
)
op.add_column(
"validator_log",
sa.Column(
"type",
sa.String(),
nullable=True,
comment="Validator type (ValidatorType enum value)",
),
)
op.add_column(
"validator_log",
sa.Column(
"metadata",
JSONB,
nullable=True,
comment="Full resolved validator config used for this run",
),
)


def downgrade() -> None:
op.drop_column("validator_log", "metadata")
op.drop_column("validator_log", "type")
op.drop_column("validator_log", "stage")
op.drop_column("validator_log", "duration_ms")
op.drop_column("validator_log", "order")
op.drop_column("request_log", "metadata")
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Rename validator_log.request_id to request_log_id

The column is a foreign key to request_log.id (the surrogate PK), not to
request_log.request_id (the caller-supplied business id) - the old name
made it easy to join on the wrong column.

Revision ID: 011
Revises: 010
Create Date: 2026-09-04 00:00:00.000000

"""

from typing import Sequence, Union

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "011"
down_revision = "010"
branch_labels = None
depends_on = None


def upgrade() -> None:
op.alter_column("validator_log", "request_id", new_column_name="request_log_id")
op.execute(
"ALTER INDEX idx_validator_log_request_id RENAME TO idx_validator_log_request_log_id"
)


def downgrade() -> None:
op.execute(
"ALTER INDEX idx_validator_log_request_log_id RENAME TO idx_validator_log_request_id"
)
op.alter_column("validator_log", "request_log_id", new_column_name="request_id")
Loading
Loading