skyl sits on the path between your users' prompts and a third-party vendor, and the gateway proxies paid APIs. This page states the boundaries explicitly, because "it depends" is not an answer a security review accepts.
Trust boundaries#
| Boundary | What crosses it | Who controls it |
|---|---|---|
| Your process → provider | The entire Request, plus one credential header | You choose the provider and the host |
| Gateway caller → gateway | A ChatRequest and a bearer token | You issue the tokens |
| Gateway → provider | The provider credential | The operator holds it; callers never see it |
| Model output → your code | Untrusted input | Yours to validate |
Model output is untrusted input#
var args struct {
City string `json:"city"`
}
if err := json.Unmarshal(call.Arguments, &args); err != nil {
return skyl.ToolErrorMessage(call.ID, "invalid arguments")
}
// The schema said "string". It did not say "a city you have heard of".
if !knownCity(args.City) {
return skyl.ToolErrorMessage(call.ID, "unknown city")
}var args struct {
City string `json:"city"`
}
if err := json.Unmarshal(call.Arguments, &args); err != nil {
return skyl.ToolErrorMessage(call.ID, "invalid arguments")
}
// The schema said "string". It did not say "a city you have heard of".
if !knownCity(args.City) {
return skyl.ToolErrorMessage(call.ID, "unknown city")
}What an authenticated gateway caller can do#
Worth stating plainly, because it defines your blast radius. With a valid token, a caller can:
- Send any prompt to any registered provider, at your expense.
- Choose any model string, including expensive ones.
- List registered providers and their models.
They cannot:
- Read your provider credentials.
- Reach a provider you have not registered.
- See another caller's traffic.
- Cause the gateway to log or persist their prompts.
A gateway token is roughly as sensitive as a provider key. Issue one per
caller via SKYL_AUTH_TOKENS so you can attribute usage and revoke
individually.
Denial of service#
| Vector | Mitigation |
|---|---|
| Unbounded concurrency | Set SKYL_MAX_CONCURRENT. Unset, a traffic spike becomes a provider rate-limit incident. |
| Long-running requests | SKYL_REQUEST_TIMEOUT, applied per handler so streams are not severed. |
| Retry amplification | Bounded by SKYL_MAX_RETRIES; backoff uses full jitter so a fleet does not synchronise. |
| Idle stream connections | Keep-alive frames plus client-disconnect cancellation — a hang-up does not leave a paid request running. |
| Large request bodies | Put a limit in a reverse proxy. The gateway does not impose one. |
Credential handling#
- Provider keys are never logged, never in an error body, never forwarded.
- Gateway tokens are compared with
subtle.ConstantTimeCompare. SKYL_AUTH_TOKENSlabels appear in logs; the tokens do not.- skyl reads no environment variables of its own — the library takes the key as an argument, so its lifetime and source are yours.
Deliberate omissions#
Certificate failures are never retried — a rejected certificate is a misconfiguration, possibly an interception attempt, and retrying delays the error an operator needs to see.
Out of scope#
- What the provider does with your prompt. Retention, jurisdiction, sub-processors, training-data terms. Read their policies.
- Prompt injection defence. skyl transports; it does not sanitise. Nothing it could do would be correct for every application.
- Model output safety. Refusals are surfaced, not enforced.
- Multi-tenant isolation inside the gateway. Callers share one process and one provider credential. If tenants must not share a credential, run a gateway per tenant.
- Exposing the sandbox. It authenticates nothing meaningfully and binds to loopback for that reason.
Reporting#
See the Security Policy. Please do not open a public issue for a vulnerability.