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
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

---

## [7.0.1] - 2026-04-11

### Fixed

- **Authentication on all endpoints** — Unified auth handling across gateway,
MCP, proxy, and API routes. Fixes 401 errors on community-saas (try.getaxonflow.com)
for gateway pre-check, audit, proxy, and MCP endpoints. Proxy routes
(dynamic policies, cost controls) were previously inaccessible in
community-saas mode.
- **Community mode tenant isolation** — Requests in community mode now
preserve per-tenant scoping. Previously all requests collapsed to a
single synthetic client, mixing audit and policy data across tenants.
- **Telemetry tracking** — All authenticated requests (including MCP and
JSON-RPC sessions) now correctly record telemetry in community-saas mode.
- **Audit identity** — Audit records now use the authenticated client identity
instead of trusting the request body, preventing cross-tenant attribution.
- **MCP server DB auth** — MCP JSON-RPC handler now validates clients
registered via database, not just the in-memory whitelist.
- **Example credentials** — 139 example files updated to read auth credentials
from environment variables, fixing failures on authenticated servers.
- **Deploy workflow** — Stack discovery excludes auxiliary services when
deploying community-saas.
- **Next.js security update** — Customer portal updated to 16.2.3
(GHSA-q4gf-8mx6-v5v3).

## [7.0.0] - 2026-04-09

### Breaking Changes
Expand Down
1 change: 1 addition & 0 deletions docker-compose.community-saas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ services:
axonflow-orchestrator:
environment:
DEPLOYMENT_MODE: community-saas
ORG_ID: community-saas
OLLAMA_ENDPOINT: http://ollama:11434
OLLAMA_MODEL: llama3.2:latest

Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ services:
DEPLOYMENT_MODE: ${DEPLOYMENT_MODE:-community}
AXONFLOW_INTEGRATIONS: ${AXONFLOW_INTEGRATIONS:-}
AXONFLOW_LICENSE_KEY: ${AXONFLOW_LICENSE_KEY:-}
AXONFLOW_VERSION: "${AXONFLOW_VERSION:-7.0.0}"
AXONFLOW_VERSION: "${AXONFLOW_VERSION:-7.0.1}"

# Media governance (v4.5.0+) - set to "true" to enable in Community mode
MEDIA_GOVERNANCE_ENABLED: ${MEDIA_GOVERNANCE_ENABLED:-}
Expand Down Expand Up @@ -223,7 +223,7 @@ services:
PORT: 8081
DEPLOYMENT_MODE: ${DEPLOYMENT_MODE:-community}
AXONFLOW_LICENSE_KEY: ${AXONFLOW_LICENSE_KEY:-}
AXONFLOW_VERSION: "${AXONFLOW_VERSION:-7.0.0}"
AXONFLOW_VERSION: "${AXONFLOW_VERSION:-7.0.1}"

# Media governance (v4.5.0+) - set to "true" to enable in Community mode
MEDIA_GOVERNANCE_ENABLED: ${MEDIA_GOVERNANCE_ENABLED:-}
Expand Down
4 changes: 0 additions & 4 deletions examples/audit-logging/http/audit-logging.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ PRECHECK_START=$(get_ms)
PRECHECK_RESPONSE=$(curl -s -X POST "$AGENT_URL/api/policy/pre-check" \
-H "Content-Type: application/json" \
"${AUTH_HEADER[@]}" \
-H "Authorization: Basic $AUTH_B64" \
-d "{
\"query\": \"$QUERY\",
\"user_token\": \"$USER_TOKEN\",
Expand Down Expand Up @@ -111,7 +110,6 @@ AUDIT_START=$(get_ms)
AUDIT_RESPONSE=$(curl -s -X POST "$AGENT_URL/api/audit/llm-call" \
-H "Content-Type: application/json" \
"${AUTH_HEADER[@]}" \
-H "Authorization: Basic $AUTH_B64" \
-d "{
\"context_id\": \"$CONTEXT_ID\",
\"client_id\": \"$CLIENT_ID\",
Expand Down Expand Up @@ -166,7 +164,6 @@ TOOL_AUDIT_HTTP_CODE=$(curl -s -o /tmp/tool_audit_response.json -w "%{http_code}
-X POST "$AGENT_URL/api/v1/audit/tool-call" \
-H "Content-Type: application/json" \
"${AUTH_HEADER[@]}" \
-H "Authorization: Basic $AUTH_B64" \
-d "{
\"tool_name\": \"weather-api\",
\"tool_type\": \"api\",
Expand Down Expand Up @@ -237,7 +234,6 @@ echo ""
SEARCH_RESPONSE=$(curl -s -X POST "$AGENT_URL/api/v1/audit/search" \
-H "Content-Type: application/json" \
"${AUTH_HEADER[@]}" \
-H "Authorization: Basic $AUTH_B64" \
-d "{
\"client_id\": \"$CLIENT_ID\",
\"limit\": 5
Expand Down
6 changes: 3 additions & 3 deletions examples/audit-logging/python/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ async def main() -> int:
print()

async with AxonFlow(
endpoint=os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080"),
endpoint=os.getenv("AXONFLOW_ENDPOINT", os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080")),
client_id=client_id,
client_secret=os.getenv("AXONFLOW_CLIENT_SECRET", "demo-secret"),
) as axonflow:
Expand Down Expand Up @@ -148,7 +148,7 @@ async def main() -> int:
# Test 3: Tool Call Audit (Non-LLM tool tracking)
print("3. Tool Call Audit (Non-LLM)")
async with AxonFlow(
endpoint=os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080"),
endpoint=os.getenv("AXONFLOW_ENDPOINT", os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080")),
client_id=client_id,
client_secret=os.getenv("AXONFLOW_CLIENT_SECRET", "demo-secret"),
) as tool_client:
Expand Down Expand Up @@ -178,7 +178,7 @@ async def main() -> int:
# Test 4: Query audit logs via SDK
print("4. Query Audit Logs")
async with AxonFlow(
endpoint=os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080"),
endpoint=os.getenv("AXONFLOW_ENDPOINT", os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080")),
client_id=client_id,
client_secret=os.getenv("AXONFLOW_CLIENT_SECRET", "demo-secret"),
) as query_client:
Expand Down
2 changes: 0 additions & 2 deletions examples/code-governance/go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,3 @@ module github.com/axonflow/examples/code-governance
go 1.21

require github.com/getaxonflow/axonflow-sdk-go/v5 v5.3.0


2 changes: 1 addition & 1 deletion examples/code-governance/go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func main() {

// Initialize AxonFlow client
client := axonflow.NewClient(axonflow.AxonFlowConfig{
Endpoint: getEnv("AXONFLOW_AGENT_URL", "http://localhost:8080"),
Endpoint: getEnv("AXONFLOW_ENDPOINT", getEnv("AXONFLOW_AGENT_URL", "http://localhost:8080")),
ClientID: getEnv("AXONFLOW_CLIENT_ID", "demo-client"),
ClientSecret: getEnv("AXONFLOW_CLIENT_SECRET", "demo-secret"),
})
Expand Down
2 changes: 1 addition & 1 deletion examples/code-governance/python/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ async def main() -> int:
user_token = os.getenv("AXONFLOW_USER_TOKEN", "")

async with AxonFlow(
endpoint=os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080"),
endpoint=os.getenv("AXONFLOW_ENDPOINT", os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080")),
client_id=os.getenv("AXONFLOW_CLIENT_ID", "demo-client"),
client_secret=os.getenv("AXONFLOW_CLIENT_SECRET", "demo-secret"),
) as ax:
Expand Down
2 changes: 1 addition & 1 deletion examples/cost-controls/enforcement/go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func main() {
// Otherwise cached responses bypass budget enforcement
// Note: Must set TTL to non-zero to prevent SDK defaults from enabling cache
client := axonflow.NewClient(axonflow.AxonFlowConfig{
Endpoint: getEnv("AXONFLOW_AGENT_URL", "http://localhost:8080"),
Endpoint: getEnv("AXONFLOW_ENDPOINT", getEnv("AXONFLOW_AGENT_URL", "http://localhost:8080")),
ClientID: getEnv("AXONFLOW_CLIENT_ID", "demo-client"),
ClientSecret: getEnv("AXONFLOW_CLIENT_SECRET", "demo-secret"),
Cache: axonflow.CacheConfig{Enabled: false, TTL: time.Nanosecond},
Expand Down
2 changes: 1 addition & 1 deletion examples/cost-controls/enforcement/python/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def __init__(self):
self.budget_id = f"enforcement-test-{int(time.time())}"

self.client = AxonFlow.sync(
endpoint=os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080"),
endpoint=os.getenv("AXONFLOW_ENDPOINT", os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080")),
client_id=os.getenv("AXONFLOW_CLIENT_ID", "demo-client"),
client_secret=os.getenv("AXONFLOW_CLIENT_SECRET", "demo-secret"),
)
Expand Down
35 changes: 22 additions & 13 deletions examples/cost-controls/http/cost-controls.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,19 @@

set -e

BASE_URL="${AXONFLOW_AGENT_URL:-http://localhost:8080}"
BASE_URL="${AXONFLOW_ENDPOINT:-${AXONFLOW_AGENT_URL:-http://localhost:8080}}"
ORG_ID="demo-org"
BUDGET_ID="demo-budget-http-$(date +%s)"

# Build auth args for curl (Basic auth if credentials are set)
CURL_AUTH=()
if [ -n "${AXONFLOW_CLIENT_ID:-}" ] && [ -n "${AXONFLOW_CLIENT_SECRET:-}" ]; then
CURL_AUTH=(-u "${AXONFLOW_CLIENT_ID}:${AXONFLOW_CLIENT_SECRET}")
fi

# Wrapper: curl with auth
acurl() { curl "${CURL_AUTH[@]}" "$@"; }

echo "AxonFlow Cost Controls - HTTP (curl) - Comprehensive"
echo "====================================================="
echo ""
Expand All @@ -34,7 +43,7 @@ echo ""

# 1. POST /api/v1/budgets - Create a budget
echo "1. POST /api/v1/budgets - Creating a monthly budget..."
BUDGET_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/v1/budgets" \
BUDGET_RESPONSE=$(acurl -s -X POST "${BASE_URL}/api/v1/budgets" \
-H "Content-Type: application/json" \
-H "X-Org-ID: ${ORG_ID}" \
-d '{
Expand All @@ -52,22 +61,22 @@ echo ""

# 2. GET /api/v1/budgets/{id} - Get a budget
echo "2. GET /api/v1/budgets/{id} - Getting budget by ID..."
GET_RESPONSE=$(curl -s "${BASE_URL}/api/v1/budgets/${BUDGET_ID}")
GET_RESPONSE=$(acurl -s "${BASE_URL}/api/v1/budgets/${BUDGET_ID}")
echo " Response: ${GET_RESPONSE}" | head -c 200
echo ""
echo ""

# 3. GET /api/v1/budgets - List budgets
echo "3. GET /api/v1/budgets - Listing all budgets..."
LIST_RESPONSE=$(curl -s "${BASE_URL}/api/v1/budgets?limit=5" \
LIST_RESPONSE=$(acurl -s "${BASE_URL}/api/v1/budgets?limit=5" \
-H "X-Org-ID: ${ORG_ID}")
echo " Response: ${LIST_RESPONSE}" | head -c 300
echo ""
echo ""

# 4. PUT /api/v1/budgets/{id} - Update a budget
echo "4. PUT /api/v1/budgets/{id} - Updating budget limit..."
UPDATE_RESPONSE=$(curl -s -X PUT "${BASE_URL}/api/v1/budgets/${BUDGET_ID}" \
UPDATE_RESPONSE=$(acurl -s -X PUT "${BASE_URL}/api/v1/budgets/${BUDGET_ID}" \
-H "Content-Type: application/json" \
-d '{
"name": "Demo Budget (HTTP) - Updated",
Expand All @@ -83,21 +92,21 @@ echo ""

# 5. GET /api/v1/budgets/{id}/status - Get budget status
echo "5. GET /api/v1/budgets/{id}/status - Checking budget status..."
STATUS_RESPONSE=$(curl -s "${BASE_URL}/api/v1/budgets/${BUDGET_ID}/status")
STATUS_RESPONSE=$(acurl -s "${BASE_URL}/api/v1/budgets/${BUDGET_ID}/status")
echo " Response: ${STATUS_RESPONSE}" | head -c 300
echo ""
echo ""

# 6. GET /api/v1/budgets/{id}/alerts - Get budget alerts
echo "6. GET /api/v1/budgets/{id}/alerts - Getting budget alerts..."
ALERTS_RESPONSE=$(curl -s "${BASE_URL}/api/v1/budgets/${BUDGET_ID}/alerts")
ALERTS_RESPONSE=$(acurl -s "${BASE_URL}/api/v1/budgets/${BUDGET_ID}/alerts")
echo " Response: ${ALERTS_RESPONSE}" | head -c 200
echo ""
echo ""

# 7. POST /api/v1/budgets/check - Check if request allowed
echo "7. POST /api/v1/budgets/check - Pre-flight budget check..."
CHECK_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/v1/budgets/check" \
CHECK_RESPONSE=$(acurl -s -X POST "${BASE_URL}/api/v1/budgets/check" \
-H "Content-Type: application/json" \
-d '{
"org_id": "'"${ORG_ID}"'"
Expand All @@ -111,22 +120,22 @@ echo ""

# 8. GET /api/v1/usage - Get usage summary
echo "8. GET /api/v1/usage - Getting usage summary..."
USAGE_RESPONSE=$(curl -s "${BASE_URL}/api/v1/usage?period=monthly" \
USAGE_RESPONSE=$(acurl -s "${BASE_URL}/api/v1/usage?period=monthly" \
-H "X-Org-ID: ${ORG_ID}")
echo " Response: ${USAGE_RESPONSE}"
echo ""

# 9. GET /api/v1/usage/breakdown - Get usage breakdown
echo "9. GET /api/v1/usage/breakdown - Getting usage breakdown by provider..."
BREAKDOWN_RESPONSE=$(curl -s "${BASE_URL}/api/v1/usage/breakdown?group_by=provider&period=monthly" \
BREAKDOWN_RESPONSE=$(acurl -s "${BASE_URL}/api/v1/usage/breakdown?group_by=provider&period=monthly" \
-H "X-Org-ID: ${ORG_ID}")
echo " Response: ${BREAKDOWN_RESPONSE}" | head -c 300
echo ""
echo ""

# 10. GET /api/v1/usage/records - List usage records
echo "10. GET /api/v1/usage/records - Listing usage records..."
RECORDS_RESPONSE=$(curl -s "${BASE_URL}/api/v1/usage/records?limit=5" \
RECORDS_RESPONSE=$(acurl -s "${BASE_URL}/api/v1/usage/records?limit=5" \
-H "X-Org-ID: ${ORG_ID}")
echo " Response: ${RECORDS_RESPONSE}" | head -c 300
echo ""
Expand All @@ -138,7 +147,7 @@ echo ""

# 11. GET /api/v1/pricing - Get pricing info
echo "11. GET /api/v1/pricing - Getting model pricing..."
PRICING_RESPONSE=$(curl -s "${BASE_URL}/api/v1/pricing?provider=anthropic&model=claude-sonnet-4")
PRICING_RESPONSE=$(acurl -s "${BASE_URL}/api/v1/pricing?provider=anthropic&model=claude-sonnet-4")
echo " Response: ${PRICING_RESPONSE}"
echo ""

Expand All @@ -148,7 +157,7 @@ echo ""

# 12. DELETE /api/v1/budgets/{id} - Delete a budget
echo "12. DELETE /api/v1/budgets/{id} - Cleaning up..."
DELETE_RESPONSE=$(curl -s -X DELETE "${BASE_URL}/api/v1/budgets/${BUDGET_ID}" \
DELETE_RESPONSE=$(acurl -s -X DELETE "${BASE_URL}/api/v1/budgets/${BUDGET_ID}" \
-w "HTTP %{http_code}")
echo " Response: ${DELETE_RESPONSE}"
echo ""
Expand Down
3 changes: 3 additions & 0 deletions examples/cost-estimation/go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ func doRequest(method, url, body string, headers map[string]string) (int, map[st
for k, v := range headers {
req.Header.Set(k, v)
}
if clientID, clientSecret := os.Getenv("AXONFLOW_CLIENT_ID"), os.Getenv("AXONFLOW_CLIENT_SECRET"); clientID != "" && clientSecret != "" {
req.SetBasicAuth(clientID, clientSecret)
}

client := &http.Client{Timeout: 15 * time.Second}
resp, err := client.Do(req)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ WAITED=0
POLL_INTERVAL=5

while [ $WAITED -lt $MAX_WAIT ]; do
EXEC_LIST=$(curl -s --max-time 10 "${AGENT_URL}/api/v1/executions?limit=5" 2>/dev/null || echo '{"executions":[]}')
EXEC_LIST=$(curl -s --max-time 10 -H "Authorization: Basic $AUTH_B64" "${AGENT_URL}/api/v1/executions?limit=5" 2>/dev/null || echo '{"executions":[]}')

# Prefer matching our REQUEST_ID, fall back to any completed execution.
# The executions API returns request_id as the primary identifier.
Expand Down Expand Up @@ -161,6 +161,7 @@ echo "4. GET /api/v1/executions/${EXECUTION_ID} - Validate total cost..."

DETAIL_HTTP_CODE=$(curl -s -o /tmp/axonflow_exec_cost_detail.json -w "%{http_code}" \
--max-time 15 \
-H "Authorization: Basic $AUTH_B64" \
"${AGENT_URL}/api/v1/executions/${EXECUTION_ID}" || echo "000")

DETAIL_RESPONSE=$(cat /tmp/axonflow_exec_cost_detail.json 2>/dev/null || echo "{}")
Expand Down Expand Up @@ -189,6 +190,7 @@ echo "5. GET /api/v1/executions/${EXECUTION_ID}/steps - Validate step costs..."

STEPS_HTTP_CODE=$(curl -s -o /tmp/axonflow_exec_cost_steps.json -w "%{http_code}" \
--max-time 15 \
-H "Authorization: Basic $AUTH_B64" \
"${AGENT_URL}/api/v1/executions/${EXECUTION_ID}/steps" || echo "000")

STEPS_RESPONSE=$(cat /tmp/axonflow_exec_cost_steps.json 2>/dev/null || echo "{}")
Expand Down
4 changes: 2 additions & 2 deletions examples/demo/02_governed_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@

async def main():
async with AxonFlow(
endpoint=os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080"),
endpoint=os.getenv("AXONFLOW_ENDPOINT", os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080")),
client_id=os.getenv("AXONFLOW_CLIENT_ID", "demo-client"),
client_secret=os.getenv("AXONFLOW_CLIENT_SECRET", "demo-secret"),
) as ax:
response = await ax.proxy_llm_call(
user_token="demo-user",
user_token=os.getenv("AXONFLOW_USER_TOKEN", "demo-user"),
query="Explain AI governance in one sentence",
request_type="chat",
)
Expand Down
6 changes: 3 additions & 3 deletions examples/demo/02_pii_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async def test_pii_detection(client: AxonFlow) -> None:
try:
# Use pre-check to test policy enforcement
ctx = await client.get_policy_approved_context(
user_token="demo-user",
user_token=os.getenv("AXONFLOW_USER_TOKEN", "demo-user"),
query=test["query"],
)

Expand Down Expand Up @@ -106,7 +106,7 @@ async def test_safe_query(client: AxonFlow) -> None:
print(f"Query: \"{query}\"")

ctx = await client.get_policy_approved_context(
user_token="demo-user",
user_token=os.getenv("AXONFLOW_USER_TOKEN", "demo-user"),
query=query,
)

Expand All @@ -122,7 +122,7 @@ async def test_safe_query(client: AxonFlow) -> None:


async def main():
agent_url = os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080")
agent_url = os.getenv("AXONFLOW_ENDPOINT", os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080"))

async with AxonFlow(
endpoint=agent_url,
Expand Down
4 changes: 2 additions & 2 deletions examples/demo/03_pii_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@

async def main():
async with AxonFlow(
endpoint=os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080"),
endpoint=os.getenv("AXONFLOW_ENDPOINT", os.getenv("AXONFLOW_AGENT_URL", "http://localhost:8080")),
client_id=os.getenv("AXONFLOW_CLIENT_ID", "demo-client"),
client_secret=os.getenv("AXONFLOW_CLIENT_SECRET", "demo-secret"),
) as ax:
try:
response = await ax.proxy_llm_call(
user_token="demo-user",
user_token=os.getenv("AXONFLOW_USER_TOKEN", "demo-user"),
query="My SSN is 123-45-6789. Can you check my taxes?",
request_type="chat",
)
Expand Down
Loading
Loading