Deno --allow-net=api.sume.com is not enough to save a finished video

Sume's content URL answers 302 to a file host. A Deno script with a narrow --allow-net fails at the second fetch: read the redirect host, then allow it.

4 min readSume
All posts

Deno runs a script with no network access until you grant it, and it can limit that access to named hosts. That is a good default for a script that holds an API key, and it has one catch with Sume's video API. A finished job lists unsigned_urls, and the first entry is a /content?index=0 URL on the API that answers 302 with the location of the file. If you allow only the API host, the redirect target is a second host that the permission flag does not cover.

Deno's own documentation describes --allow-net as taking a list of hosts, and says a script with no flag has no network access. This post shows what happens, and a way to handle it without allowing every host.

The script

It submits the cheapest Gemini Omni Flash job, 3 seconds at 360p, which Sume prices at $0.1125. It polls the polling_url from the 202, then requests the content URL with redirect: "manual" so it can read the location header itself. It prints the host, compares it with FILE_HOST, and only then fetches the file, with no Authorization header.

const base = Deno.env.get("SUME_API_BASE") ?? "https://api.sume.com";
const auth = { Authorization: `Bearer ${Deno.env.get("SUME_API_KEY")}` };
const wait = Number(Deno.env.get("POLL_SECONDS") ?? 30) * 1000;

const submit = await fetch(`${base}/v1/videos`, {
  method: "POST",
  headers: { ...auth, "Content-Type": "application/json", "Idempotency-Key": "deno-omni-0001" },
  body: JSON.stringify({ model: "gemini-omni-flash-1.1", prompt: "A lighthouse in a storm",
    duration: 3, resolution: "360p", aspect_ratio: "16:9" }),
});
if (submit.status !== 202) throw new Error(await submit.text());
const { polling_url } = await submit.json();

let job;
do {
  await new Promise((r) => setTimeout(r, wait));
  job = await (await fetch(polling_url, { headers: auth })).json();
  if (job.status === "failed" || job.status === "cancelled") throw new Error(job.status);
} while (job.status !== "completed");

const hop = await fetch(job.unsigned_urls[0], { headers: auth, redirect: "manual" });
const target = new URL(hop.headers.get("location")!);
console.log("file host:", target.host);
if (target.host !== Deno.env.get("FILE_HOST")) throw new Error("unexpected file host");
const file = await fetch(target);
await Deno.writeFile("out.mp4", new Uint8Array(await file.arrayBuffer()));
console.log("cost", job.usage.cost);

What I saw

I ran it against a local stand-in with the API on one port and the file on another, with a short poll delay. Run with --allow-net for the API host only, it printed the file host and then stopped with a NotCapable error naming the second host. With both hosts allowed it saved the file and printed cost 0.1125. With a wrong FILE_HOST it refused to fetch at all. The stand-in did not receive the key on the second request.

The check is not specific to the stand-in. The Sume guide documents the 302 but not the address it points to, so the script learns the host at run time. The first run prints it, you confirm it is what you expect, and you set FILE_HOST and extend the flag.

Flags for the command line

Each flag grants one thing and no more.

Deno permission flags for the script, per Deno's security guide and Sume's video guide (read 2026-10-05)
FlagGrantsNeeded because
--allow-net=api.sume.com,FILE_HOSTTwo named hostsSubmit and poll on the API, then the file
--allow-envEnvironment readsThe key, base URL and poll delay
--allow-write=out.mp4One output pathThe saved clip

Why not allow everything

A bare --allow-net would make the script work on the first try, and it would also let any dependency or edited line send your key anywhere. Pinning two hosts is a few seconds of setup that keeps the API key where it belongs. The same reasoning applies when you pin the model: read the catalog at GET /v1/videos/models before you hard-code an id, as in /blog/video-catalog-input-matrix-python-frame-and-reference-fields.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume