Authentication
Section titled “Authentication”Hoody Exec’s @token magic comment adds shared-secret authentication to any script, covering both HTTP requests and WebSocket connections. You do not add middleware, an auth library, or JWT infrastructure.
// @token my-secret-key-123
return { data: 'only authenticated requests see this' };Unauthenticated requests get 401 Unauthorized. Authenticated requests run your script normally.
The token check
Section titled “The token check”- You add
// @token <secret>at the top of your script. - The server checks every incoming request for a matching token before your code runs.
- A matching token lets the script execute normally.
- A missing or wrong token returns
401 Unauthorized, and your code never runs.
The token check happens at the server level, before VM creation and metadata construction. There is no way for a script to see or override the gate.
Credential methods
Section titled “Credential methods”A client can send the token four ways. The server checks them in the order below and uses the first one present.
Priority order
Section titled “Priority order”| Priority | Method | Header / Parameter |
|---|---|---|
| 1a | Bearer token | Authorization: Bearer <token> |
| 1b | Basic auth (password field) | Authorization: Basic base64(user:token) |
| 2 | X-Token header | X-Token: <token> |
| 3 | Query parameter | ?token=<token> |
If multiple sources are present, only the highest-priority one is used. For example, if both Authorization: Bearer and X-Token are sent, only the Bearer value is checked.
Bearer token
Section titled “Bearer token”Bearer is the standard approach for API clients and SDKs.
curl -H "Authorization: Bearer my-secret-key-123" \ "https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/data"const res = await fetch('https://your-endpoint.containers.hoody.com/api/data', { headers: { 'Authorization': 'Bearer my-secret-key-123' }});import requestsres = requests.get('https://your-endpoint.containers.hoody.com/api/data', headers={'Authorization': 'Bearer my-secret-key-123'})One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Runs the same authenticated request from a browser address bar, with the token sent as a Bearer credential.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/data&method=GET&bearer_token=my-secret-key-123&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
The scheme name is case-insensitive (Bearer, bearer, BEARER all work).
Basic auth
Section titled “Basic auth”The server matches the @token value against the password field of HTTP Basic auth. It ignores the username, so send anything or nothing.
That makes @token work directly with:
curl -uflag- Browser native auth dialogs
- HTTP clients that only support Basic auth
- Legacy systems and webhook integrations
# Username "admin", password is the token; the username is ignoredcurl -u admin:my-secret-key-123 \ "https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/data"# No username, just the token as passwordcurl -u :my-secret-key-123 \ "https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/data"# Any username works; only the password matterscurl -u monitoring-bot:my-secret-key-123 \ "https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/data"X-Token header
Section titled “X-Token header”A custom header, useful when a proxy or gateway has already claimed Authorization.
curl -H "X-Token: my-secret-key-123" \ "https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/data"Query parameter
Section titled “Query parameter”Pass the token in the URL. This suits quick testing, webhook callbacks, and browser links.
curl "https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/data?token=my-secret-key-123"Rejection response
Section titled “Rejection response”When authentication fails, the server returns:
HTTP/1.1 401 UnauthorizedWWW-Authenticate: Bearer realm="hoody-exec"Content-Type: application/json
{ "error": "Unauthorized", "message": "This endpoint requires authentication. Provide a valid token via Authorization: Bearer <token> header, X-Token header, ?token= query parameter, or HTTP Basic Auth."}WWW-Authenticate: Bearertriggers the native auth dialog in browsers.- The response body is the same whether the token is missing or wrong, so the reply leaks nothing about which one it was.
- If the script configures CORS (
@cors), the 401 response carries the CORS headers too.
CORS preflight and tokens
Section titled “CORS preflight and tokens”When a script carries both @token and @cors, the server answers CORS preflight requests (OPTIONS) before the token check:
// @token my-secret-key-123// @cors reflective
return { data: 'protected + CORS-enabled' };Browsers send an OPTIONS preflight before any cross-origin request, and the HTTP spec does not let a preflight carry credentials. Requiring a token on the preflight would break CORS for every browser client.
The exchange runs in two steps:
- The browser sends
OPTIONSwithOriginandAccess-Control-Request-Method, and the server returns204with the CORS headers. No token required. - The browser sends the real
GETorPOSTwithAuthorization: Bearer <token>, and the server checks the token before running the script.
This follows the CORS spec and is not a way around the gate: the request that reaches your code is still checked.
WebSocket authentication
Section titled “WebSocket authentication”The @token gate also protects WebSocket connections. The check runs on the HTTP upgrade request, before the WebSocket handshake completes.
// @token my-secret-key-123// @mode worker// @websocket
ws.on('message', (socket, data) => socket.send('echo:' + data));Send the token when you connect:
// Works in all WebSocket clients (including browsers)// The WebSocket URL is the script's own route (file-based routing), not a dedicated /ws pathconst ws = new WebSocket('wss://your-endpoint.containers.hoody.com/realtime/echo?token=my-secret-key-123');// Node.js / Bun (browsers don't allow custom WebSocket headers)const ws = new WebSocket('wss://your-endpoint.containers.hoody.com/realtime/echo', { headers: { 'Authorization': 'Bearer my-secret-key-123' }});Without a valid token, the server writes HTTP/1.1 401 Unauthorized to the socket and closes the connection. The WebSocket onerror / onclose event fires on the client.
Security details
Section titled “Security details”Constant-time comparison
Section titled “Constant-time comparison”Token comparison uses SHA-256 hashing on both sides followed by crypto.timingSafeEqual:
SHA-256(provided) === SHA-256(expected) // constant-timeThis prevents timing attacks: the comparison takes the same amount of time regardless of how many characters match.
Token redaction
Section titled “Token redaction”The token value is not returned in any API response or written to any log:
| Surface | Redaction |
|---|---|
| Access logs | ?token= replaced with ?token=[REDACTED] |
| Referer headers in logs | ?token= redacted |
Scripts API (/api/v1/exec/scripts/read) | Raw content shows // @token [REDACTED] |
| Magic Comments API | token field returns [REDACTED] |
metadata.parameters in your script | ?token= query param is removed before your code runs |
Encoded bypass attempts (%74oken=) | Caught by URL-parser fallback and redacted |
Token isolation
Section titled “Token isolation”When a client authenticates with ?token=, the server strips the token from the request URL before your script runs. Your script never sees it:
// @token my-secret
// Client calls: /api/data?token=my-secret&page=2// Your script sees:metadata.parameters // → { page: "2" } (no "token" key)metadata.path // → /api/data (clean)Examples
Section titled “Examples”Protected API endpoint
Section titled “Protected API endpoint”// @token sk_prod_a8f2e9c1d4b6// @cors reflective
const userId = metadata.parameters.id;const user = await db.getUser(userId);return user;Webhook receiver with token
Section titled “Webhook receiver with token”// @token whsec_github_abc123
if (req.method !== 'POST') { res.statusCode = 405; return { error: 'Method Not Allowed' };}
const payload = JSON.parse(req.rawBody.toString());await processWebhook(payload);return { received: true };# GitHub webhook configured with:# URL: https://your-endpoint.containers.hoody.com/webhooks/github?token=whsec_github_abc123Token with worker mode and WebSocket
Section titled “Token with worker mode and WebSocket”// @token realtime-secret-456// @mode worker// @websocket// @cors reflective
if (!shared.connections) shared.connections = new Set();
ws.on('open', (socket) => shared.connections.add(socket));ws.on('close', (socket) => shared.connections.delete(socket));ws.on('message', (socket, data) => { // Broadcast to all connected clients for (const client of shared.connections) { if (client !== socket) client.send(data); }});Programmatic management
Section titled “Programmatic management”# Read magic comments (token is redacted in response)hoody exec magic-comments read -c CONTAINER_ID --path "api/data.ts" -o json# → { "path": "api/data.ts", "comments": { "token": "[REDACTED]", "cors": "reflective" } }const containerClient = await client.withContainer({ id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER});
// Read magic comments (token is redacted)const comments = await containerClient.exec.magic.read({ path: 'api/data.ts' });console.log(comments.data.comments); // { token: "[REDACTED]", cors: "reflective" }
// Update the tokenawait containerClient.exec.magic.updateHandler({ path: 'api/data.ts', comments: { token: 'new-secret-key' }});# Read magic comments (token is redacted in response)curl "https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/v1/exec/magic-comments/read?path=api/data.ts"# → { "path": "api/data.ts", "comments": { "token": "[REDACTED]", "cors": "reflective" } }
# Update token via APIcurl -X PUT "https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/v1/exec/magic-comments/update" \ -H "Content-Type: application/json" \ -d '{ "path": "api/data.ts", "comments": { "token": "new-secret-key" } }'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Reads or updates the script’s magic comments; the token value itself is always redacted in the response.
# Read
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/v1/exec/magic-comments/read?path=api/data.ts&method=GET&response=transparent
# Update
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/v1/exec/magic-comments/update&method=PUT&json={"path":"api/data.ts","comments":{"token":"new-secret-key"}}&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.