# Security

> What leaves your environment, what Raylight stores, and how secrets and links are protected.

## Push only

Raylight never pulls from your systems. There is no GitHub App, no OAuth grant to your source host, deploy platform or chat tool, and nothing in Raylight that can call them. Every production is pushed in by something you run, and every update is pushed back out through webhooks, email and signed links. On your side, the attack surface is one outbound HTTPS call with a key you can revoke.

## What leaves your environment

| Integration | What is sent |
| --- | --- |
| REST, SDK, CLI | The brief you build: `intent`, `context`, media URLs, the code files you chose (path, source, and, if you computed them, status, line counts, patch, digest, score), format, brand, `externalId`, `origin`. Nothing from your machine that you did not put in the body. |
| GitHub Action | The brief it builds on your runner: the intent input, the release notes or PR body as context (on a push, the head commit's message), changed files matching `code-globs` with their diff hunks, format inputs, an origin naming the repo and actor, and an externalId. No repository token, no environment variables, no files outside the globs. Open source at `github.com/raylight-labs/video-action`. |
| MCP | Whatever the agent passes to `create_production`: intent, any context and files it chose from your workspace, media URLs. The agent runs on your side; the server only sees tool arguments. Editor tools move project content between your open tab and the client. |
| Zapier and other no-code | The JSON body you map in the action step. |

## What Raylight stores

- The brief as sent, including code sources and patches, so the pitch can be rewritten when you send comments and the video built. It lives on the production in your workspace and is deleted with it.
- The production's events: stage changes, every pitch version, comments, who greenlit it and when, the delivery links.
- Webhook endpoints, their secrets (encrypted, see below) and the delivery log: event, attempts, your server's last status and the first 2 KB of its response body.
- API keys as hashes with a display prefix; plaintext is never stored once claimed.
- Usage counters per workspace and month for the pitch cap.

## Code never travels back out

Webhook payloads, emails and greenlight links carry the production with every code file's `source` set to an empty string, `patch` set to null and `sourceOmitted` set to true. Paths, change status, line counts, digests and scores stay, so a receiver sees which files were considered but never your source. A signed link shows even less: no context, media URLs or code. Only the authenticated API and the app, which act as you, can read the full brief.

## Signing

Every delivery carries `Raylight-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">`. Verify against the raw bytes, compare in constant time, and reject timestamps more than five minutes off. For 24 hours after a secret roll, two `v1` values are present. `Raylight-Event-Id` is stable across retries, so you can de-duplicate. Node and Python code is on [Webhooks](https://www.raylight.app/docs/webhooks.md).

## Secret storage

- Webhook secrets are 32 random bytes, prefixed `whsec_`, shown once. At rest they are encrypted with AES-256-GCM under a server key kept outside the database, so a database leak alone does not expose a usable secret.
- API keys are stored as hashes. Device-flow plaintext is held only between approval and claim (10 minutes at most) and wiped on claim or expiry.
- Greenlight link tokens are stateless HMACs under a dedicated server secret; rotating it invalidates every link ever sent.

## Signed link scoping

A link is bound to one production and one pitch version, carries the actor it was minted for (`link:<email>` or `webhook:<endpointId>`), and expires after 30 days. It stops working early if that person leaves the workspace or that endpoint is deleted. It can read and answer that pitch, nothing more: no other productions, no context, media or code, nothing else in the workspace. The page behind it acts only on an explicit POST, never on load, so link prefetchers and mail scanners cannot greenlight anything.

## Transport and keys

- HTTPS everywhere. Webhook URLs must be https and public; private and loopback addresses are refused at registration and at delivery.
- API keys only work on `/v1`; a key cannot read account data, projects or billing.
- Keys are scoped (`productions`, `renders`, `capture`, `import`) and can be revoked instantly in the app.
- MCP uses OAuth 2.0 with PKCE; the token is bound to the client that requested it and the account that approved it.

## Reporting an issue

Email [cyrus@raylight.app](mailto:cyrus@raylight.app) with what you found, how to reproduce it, and whether you think anyone else's data was reachable. We read reports the same day and will tell you what we changed.

## More

- Previous: [Limits and pricing](https://www.raylight.app/docs/limits.md)
- All docs: https://www.raylight.app/llms.txt
- Everything in one file: https://www.raylight.app/llms-full.txt
- OpenAPI: https://api.raylight.app/v1/openapi.json
