blob-storage — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited blob-storage (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Val Town provides built-in key/value blob storage via the std/blob module. Reach for it whenever a val needs to persist simple values — JSON documents, cached API responses, uploaded files, or binary assets — keyed by a string. For relational or structured data you query with SQL, prefer std/sqlite instead.
There are two exports of the blob utility: global.ts, which is scoped to the user account, and main.ts, which is scoped to the val itself. Prefer the main.ts interface and val scoping for new vals.
Here is the scoped import:
/**
* Importing from `main.ts` provides an interface to val-scoped blobs.
*/
import { blob } from "https://esm.town/v/std/blob/main.ts";Here are the global imports:
/**
* Importing from `global.ts` provides a blob interface that is scoped
* to your account.
*/
import { blob } from "https://esm.town/v/std/blob/global.ts";
/**
* This entrypoint is also available as `v/std/blob`. This is common
* in older vals.
*/
import { blob } from "https://esm.town/v/std/blob";The scoped & global blob interfaces have the same methods.
Scoped & global blobs are stored separately: you cannot access global blobs with the scoped interface or vice versa.
import { blob } from "https://esm.town/v/std/blob/main.ts";
await blob.setJSON("config", { theme: "dark", count: 0 });
const config = await blob.getJSON("config");
// config = { theme: "dark", count: 0 }, or undefined if the key doesn't existgetJSON returns undefined when the key is missing, so guard before using the result:
const config = (await blob.getJSON("config")) ?? { theme: "light", count: 0 };Use set/get for strings, binary, or any BodyInit. get returns a standard Response, so use its body helpers (.text(), .json(), .arrayBuffer(), .blob()):
await blob.set("logo.png", imageBytes); // string | BodyInit (Blob, ArrayBuffer, ReadableStream, …)
const res = await blob.get("logo.png");
const bytes = await res.arrayBuffer();Unlike getJSON, get throws ValTownBlobNotFoundError if the key doesn't exist — wrap it in try/catch when the key may be absent.
const entries = await blob.list("user_"); // optional key prefix filter
// entries = [{ key, size, lastModified }, …]
for (const { key } of entries) {
await blob.delete(key);
}
await blob.copy("config", "config.bak"); // duplicate under a new key
await blob.move("draft", "published"); // rename / relocatelist(prefix?) returns an array of { key: string; size: number; lastModified: string } — objects, not bare key strings.
std/sqlite rather than as one giant blob.When using the storeBlob, readBlob, listBlobs, or deleteBlob tools against a val owned by an organization (not your personal account), pass the org handle as the org parameter so the call hits that organization's blob storage. Example: { key: "myapp:config", org: "some-org" }. This only matters for the tool calls — code inside the val reads and writes its owning account's storage automatically. Note storeBlob accepts UTF-8 text up to 100 KB; write larger or binary blobs from code with blob.set.
feature:subkey) for organization and to scope list.getJSON returns undefined for missing keys; get throws ValTownBlobNotFoundError. Handle the absent case accordingly.Full API docs: https://docs.val.town/std/blob/
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.