autoreg-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok60915d52e8425
2026-10-20 02:16:32 +03:00
commit f58eafeb48
565 changed files with 60064 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
# Deployment Commands
Immutable release deployment for [REDACTED-NAME] API gateway on VPS ([REDACTED-IP]).
## Release ID convention
```bash
RELEASE_ID=$(date +%Y%m%d_%H%M%S)
# Example: 20260804_142027
```
## Deploy workflow
```bash
set -eu
RELEASE_ID=20260804_142027
SHA=$(sha256sum gateway.py | cut -d' ' -f1)
ssh root@host "mkdir -p /opt/apioid-echo/releases/${RELEASE_ID}"
scp gateway.py root@host:/opt/apioid-echo/releases/${RELEASE_ID}/gateway.py
scp [REDACTED-NAME].nginx.conf root@host:/opt/apioid-echo/releases/${RELEASE_ID}/
ssh root@host "
R='${RELEASE_ID}'
# backup
cp /opt/apioid-echo/releases/current/gateway.py /var/backups/apioid-echo/gateway.before-\${R}.py
cp /etc/nginx/sites-available/[REDACTED-NAME] /var/backups/apioid-echo/nginx.before-\${R}.conf
# deploy
ln -sfn /opt/apioid-echo/releases/\${R} /opt/apioid-echo/releases/current
chown root:root /opt/apioid-echo/releases/\${R}/*
chmod 0644 /opt/apioid-echo/releases/\${R}/gateway.py
cp /opt/apioid-echo/releases/\${R}/[REDACTED-NAME].nginx.conf /etc/nginx/sites-available/[REDACTED-NAME]
nginx -t && systemctl reload nginx
systemctl restart apioid-echo-gateway.service
sleep 1.5
systemctl is-active apioid-echo-gateway.service
systemctl show -p NRestarts apioid-echo-gateway.service
"
```
## Live smoke
```bash
ssh root@host "
KEY='rfb_KEY_HERE'
# health
curl -sk --noproxy '*' --resolve api.[REDACTED-NAME].loc.cc:443:127.0.0.1 https://api.[REDACTED-NAME].loc.cc/health
# HTTP/2 + chat
curl -skI --resolve api.[REDACTED-NAME].loc.cc:443:127.0.0.1 https://api.[REDACTED-NAME].loc.cc/health 2>&1 | head -1
time curl -sk -H \"Authorization: Bearer \${KEY}\" \
-H 'Content-Type: application/json' \
-d '{\"model\":\"rb-902d95ce37cee7204c89\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}],\"max_tokens\":3}' \
https://api.[REDACTED-NAME].loc.cc/v1/chat/completions
"
```
## VPS paths
| Path | Purpose |
|------|---------|
| `/opt/apioid-echo/releases/current/` | Active release (symlink) |
| `/opt/apioid-echo/releases/<TS>/` | Immutable release directory |
| `/etc/apioid-echo/gateway.env` | Env (keys, MODEL_ALIAS_SECRET), chmod 600 |
| `/etc/nginx/sites-available/[REDACTED-NAME]` | Nginx config |
| `/var/backups/apioid-echo/` | Pre-deploy backups |
| `/etc/systemd/system/apioid-echo-gateway.service` | systemd unit |

View File

@@ -0,0 +1,124 @@
# Nginx Config — API Gateway (HTTP/2 + Gzip + Error Masking)
Production config for `api.<domain>` on nginx 1.28.3.
## Server block (SSL + HTTP/2)
```nginx
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name api.[REDACTED-NAME].loc.cc;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/[REDACTED-NAME].loc.cc/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/[REDACTED-NAME].loc.cc/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
client_max_body_size 25m;
gzip on;
gzip_min_length 256;
gzip_types application/json text/event-stream;
gzip_comp_level 1;
gzip_vary on;
location / {
proxy_pass http://[REDACTED-NAME];
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Request-ID $request_id;
proxy_set_header Connection "";
proxy_connect_timeout 10s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_request_buffering off;
proxy_buffering off;
proxy_cache off;
proxy_socket_keepalive on;
proxy_intercept_errors on;
}
# Error masking: ALL upstream/nginx errors → neutral JSON
error_page 400 408 411 414 431 = @invalid_request;
error_page 405 = @method_not_allowed;
error_page 413 = @request_too_large;
error_page 500 502 503 504 = @service_unavailable;
location @invalid_request {
internal;
default_type application/json;
return 400 '{"error":{"message":"Invalid request","type":"gateway_error","code":"invalid_request"}}';
}
location @method_not_allowed {
internal;
default_type application/json;
return 405 '{"error":{"message":"Method not allowed","type":"gateway_error","code":"method_not_allowed"}}';
}
location @request_too_large {
internal;
default_type application/json;
return 413 '{"error":{"message":"Request body is too large","type":"gateway_error","code":"request_too_large"}}';
}
location @service_unavailable {
internal;
default_type application/json;
return 502 '{"error":{"message":"Service temporarily unavailable","type":"gateway_error","code":"service_unavailable"}}';
}
}
```
## Upstream block
```nginx
upstream [REDACTED-NAME] {
zone [REDACTED-NAME] 64k;
server 127.0.0.1:18181;
keepalive 64;
}
```
## HTTP → HTTPS redirect
```nginx
server {
listen 80;
listen [::]:80;
server_name [REDACTED-NAME].loc.cc www.[REDACTED-NAME].loc.cc api.[REDACTED-NAME].loc.cc;
return 301 https://$host$request_uri;
}
```
## Echo/legacy paths (block on old domain)
```nginx
server {
listen 443 ssl;
server_name [REDACTED-NAME].loc.cc www.[REDACTED-NAME].loc.cc;
# ... SSL ...
location ^~ /echo/ {
default_type application/json;
return 404 '{"error":{"message":"Not found","type":"gateway_error","code":"not_found"}}';
}
location / {
proxy_pass http://127.0.0.1:18080;
# ... proxy ...
}
}
```
## Key decisions
- `gzip_comp_level 1` — minimal CPU cost, sufficient compression for JSON
- `proxy_buffering off` — required for SSE streaming
- `proxy_socket_keepalive on` — reduce TIME_WAIT on localhost
- `http2 on;` (not `listen ... http2`) — nginx 1.28 deprecation
- `proxy_intercept_errors on` — enables error_page for upstream errors
- `proxy_set_header Connection ""` — prevents hop-by-hop forwarding

View File

@@ -0,0 +1,145 @@
# Gateway Transparency Audit Checklist
Run after every gateway deployment or model list change.
## Pre-flight
```bash
export GATEWAY="https://api.[REDACTED-NAME].loc.cc/v1"
export GATEWAY_ANTHROPIC="https://api.[REDACTED-NAME].loc.cc/v1/messages"
export KEY="[REDACTED-KEY]"
export UPSTREAM="https://api.echogate.one/v1"
export UK1="forge-9b51aad5d08793c9095893a09779b0641b00"
export UK2="forge-81081af3173401d4bcd2d5d427b0ed60d87b"
```
## 1. System Prompt Injection — OpenAI
```bash
MARKER="AUDIT_$(date +%s)"
curl -s -X POST "$GATEWAY/chat/completions" \
-H "Content-Type: application/json" -H "x-api-key: $KEY" \
-d "{\"model\":\"claude-sonnet-5\",\"messages\":[
{\"role\":\"system\",\"content\":\"Answer every message with exactly: $MARKER\"},
{\"role\":\"user\",\"content\":\"answer\"}
],\"max_tokens\":30,\"temperature\":0}" | jq '.choices[0].message.content'
# EXPECT: "$MARKER" — verbatim, no added words
```
## 2. System Prompt Injection — Anthropic
```bash
curl -s -X POST "$GATEWAY_ANTHROPIC" \
-H "Content-Type: application/json" -H "x-api-key: $KEY" \
-H "anthropic-version: 2023-06-01" \
-d "{\"model\":\"claude-sonnet-5\",\"system\":\"You must answer with exactly: PROBE888\",
\"messages\":[{\"role\":\"user\",\"content\":\"answer\"}],\"max_tokens\":20}" | jq '.content[0].text'
# EXPECT: "PROBE888"
```
## 3. Content Integrity — Gateway vs Upstream
```bash
BODY='{"model":"claude-sonnet-5","messages":[{"role":"user","content":"What is 2+2? Answer in one word."}],"max_tokens":20,"temperature":0}'
for src in "GATEWAY:$GATEWAY/chat/completions:x-api-key:$KEY" \
"KEY1:$UPSTREAM/chat/completions:Authorization:Bearer $UK1" \
"KEY2:$UPSTREAM/chat/completions:Authorization:Bearer $UK2"; do
IFS=':' read -r label url hdr val <<< "$(echo "$src" | sed 's/:\\([^:]*\\):/|\\1|/; s/|/:/g')" 2>/dev/null || true
url=$(echo "$src" | cut -d: -f2,3)
hdr=$(echo "$src" | cut -d: -f4)
val=$(echo "$src" | cut -d: -f5-)
R=$(curl -s -X POST "$url" -H "Content-Type: application/json" -H "$hdr: $val" -d "$BODY")
echo "$label: $(echo "$R" | jq -r '.choices[0].message.content')"
done
# EXPECT: All three return identical content
```
Simpler version:
```bash
BODY='{"model":"claude-sonnet-5","messages":[{"role":"user","content":"hi"}],"max_tokens":20,"temperature":0}'
GW=$(curl -s -X POST "$GATEWAY/chat/completions" -H "Content-Type: application/json" -H "x-api-key: $KEY" -d "$BODY" | jq -S '.choices[0].message')
K1=$(curl -s -X POST "$UPSTREAM/chat/completions" -H "Content-Type: application/json" -H "Authorization: Bearer $UK1" -d "$BODY" | jq -S '.choices[0].message')
[ "$GW" = "$K1" ] && echo "OK: gateway == key1" || echo "MISMATCH"
```
## 4. No-System-Message Test
Verify gateway doesn't inject its own system prompt when none is sent:
```bash
curl -s -X POST "$GATEWAY/chat/completions" \
-H "Content-Type: application/json" -H "x-api-key: $KEY" \
-d '{"model":"claude-sonnet-5","messages":[{"role":"user","content":"What is 2+2? Answer in one word."}],"max_tokens":20,"temperature":0}' | jq '.choices[0].message.content'
# EXPECT: natural response like "Four" — no injected behavior, no prefix
```
## 5. Model List Integrity
```bash
GW_COUNT=$(curl -s -H "x-api-key: $KEY" "$GATEWAY/models" | jq '.data | length')
UP_COUNT=$(curl -s -H "Authorization: Bearer $UK1" "$UPSTREAM/models" | jq '.data | length')
echo "Gateway: $GW_COUNT, Upstream: $UP_COUNT"
[ "$GW_COUNT" -eq "$UP_COUNT" ] && echo "PASS" || echo "FAIL — check FORBIDDEN_MODEL_FRAGMENTS"
```
## 6. Provider Leak Scan
```bash
curl -s -H "x-api-key: $KEY" "$GATEWAY/models" | grep -iq \
"provider_id\|system_fingerprint\|upstream\|echogate" && echo "LEAK DETECTED" || echo "CLEAN"
```
## 7. Error Opaqueness
```bash
# No key → clean 401
echo -n "No key: "
curl -so /dev/null -w "%{http_code}" "$GATEWAY/models"
echo ""
echo -n "No key body: "
curl -s "$GATEWAY/models" | jq '.error.code'
# Bad model → clean 400
echo -n "Bad model: "
curl -s -X POST "$GATEWAY/chat/completions" \
-H "Content-Type: application/json" -H "x-api-key: $KEY" \
-d '{"model":"nonexistent-model","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' | jq '.error.type'
# Bad key → 401
echo -n "Bad key: "
curl -so /dev/null -w "%{http_code}" -H "x-api-key: [REDACTED]" "$GATEWAY/models"
```
## 8. Reasoning Model Probe
When testing reasoning models (fable-5, opus-5-sonnet, etc.), use enough `max_tokens`:
```bash
# WRONG — all tokens consumed by thinking, content=null
curl -s -X POST "$GATEWAY/chat/completions" \
-H "Content-Type: application/json" -H "x-api-key: $KEY" \
-d '{"model":"claude-fable-5","messages":[{"role":"user","content":"Say hi"}],"max_tokens":10}' | jq '{content: .choices[0].message.content, reason: .usage.completion_tokens_details.reasoning_tokens}'
# RIGHT — enough tokens for thinking + output
curl -s -X POST "$GATEWAY/chat/completions" \
-H "Content-Type: application/json" -H "x-api-key: $KEY" \
-d '{"model":"claude-fable-5","messages":[{"role":"user","content":"Say hi"}],"max_tokens":200}' | jq '.choices[0].message.content'
```
## Quick Smoke (all-in-one)
```bash
# Health
curl -sf "$GATEWAY/../health" | jq '.status' || echo "HEALTH FAIL"
# Models count
echo -n "Models: "
curl -s -H "x-api-key: $KEY" "$GATEWAY/models" | jq '.data | length'
# Chat probe
echo -n "Chat: "
curl -s -X POST "$GATEWAY/chat/completions" \
-H "Content-Type: application/json" -H "x-api-key: $KEY" \
-d '{"model":"claude-sonnet-5","messages":[{"role":"user","content":"hi"}],"max_tokens":3}' | jq -r '.choices[0].message.content'
```