Skip to content
skyl

Threat Model

Trust boundaries, blast radius, and what is out of scope.

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#

BoundaryWhat crosses itWho controls it
Your process → providerThe entire Request, plus one credential headerYou choose the provider and the host
Gateway caller → gatewayA ChatRequest and a bearer tokenYou issue the tokens
Gateway → providerThe provider credentialThe operator holds it; callers never see it
Model output → your codeUntrusted inputYours to validate

Model output is untrusted input#

goCompiles
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#

VectorMitigation
Unbounded concurrencySet SKYL_MAX_CONCURRENT. Unset, a traffic spike becomes a provider rate-limit incident.
Long-running requestsSKYL_REQUEST_TIMEOUT, applied per handler so streams are not severed.
Retry amplificationBounded by SKYL_MAX_RETRIES; backoff uses full jitter so a fleet does not synchronise.
Idle stream connectionsKeep-alive frames plus client-disconnect cancellation — a hang-up does not leave a paid request running.
Large request bodiesPut 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_TOKENS labels 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.

Edit this page on GitHub