Secure time-based ephemeral storage for Hyperdrive. Automatically unlock and delete content with tamper-resistant timestamps.
This library is built with security as a core principle:
- ✅ Path traversal protection: All file paths are validated and sanitized
- ✅ Clock manipulation resistance: Monotonic clock prevents time-based attacks
- ✅ Input validation: All metadata is validated before processing
- ✅ Timestamp validation: Prevents bypass attacks with extreme values
- ✅ DoS protection: Size limits and rate limiting prevent resource exhaustion
- ✅ Deletion verification: Ensures files are actually removed
- Time-based unlocking: Content becomes visible after a specified time (
unlockAt) - Automatic deletion: Content is automatically deleted after expiration (
expiresAt) - Garbage collection: Built-in GC system with manual and automatic modes
- Hyperdrive integration: Ready-to-use adapter for Hyperdrive storage
- Secure by default: All security features enabled automatically
npm install hyperfadeimport Corestore from "corestore";
import Hyperdrive from "hyperdrive";
import { createHyperdriveEphemeralGC } from "hyperfade";
import { createMonotonicNow } from "hyperfade/lib/time.js";
// Create your Hyperdrive instance
const corestore = new Corestore("./storage");
const drive = new Hyperdrive(corestore);
// Create ephemeral GC with automatic cleanup
const { ctx, runOnce, auto } = createHyperdriveEphemeralGC(drive, {
prefix: "/sessions", // Required: where to store sessions
metaFile: "meta.json", // Optional: meta file name (default: 'meta.json')
filesToDelete: ["audio.m4a", "data.txt"], // Required: files to delete on expiration
intervalMs: 60000, // Optional: GC interval (default: 60s)
});
// Start automatic garbage collection
auto.start();
// Later, stop it
auto.stop();import { createMonotonicNow } from "hyperfade/lib/time.js";
// Use monotonic clock for security (prevents clock manipulation)
const now = createMonotonicNow();
const currentTime = now();
// Create a session that expires in 5 minutes
const meta = {
id: "session-123",
createdAt: currentTime,
updatedAt: currentTime,
expiresAt: currentTime + 5 * 60 * 1000, // 5 minutes from now
};
// Save meta (includes security validation)
await ctx.saveMeta(meta);
// Save your files
await drive.put("/sessions/session-123/audio.m4a", audioBuffer);
await drive.put("/sessions/session-123/data.txt", textBuffer);import { isVisible, isUnlocked, isExpired } from "hyperfade";
import { createMonotonicNow } from "hyperfade/lib/time.js";
const now = createMonotonicNow();
const currentTime = now();
// Load meta from Hyperdrive
const metaBuffer = await drive.get("/sessions/session-123/meta.json");
const meta = JSON.parse(metaBuffer.toString("utf8"));
// Check status
if (isVisible(meta, currentTime)) {
// Content is unlocked and not expired - safe to read
const data = await drive.get("/sessions/session-123/data.txt");
console.log("Content:", data.toString("utf8"));
} else if (!isUnlocked(meta, currentTime)) {
console.log("Content is still locked");
} else if (isExpired(meta, currentTime)) {
console.log("Content has expired");
}// Run GC once manually
const result = await runOnce();
console.log(`Expired ${result.expired} items`);See examples/delete.js for a complete example of content that automatically deletes after a set time.
import { createHyperdriveEphemeralGC } from "hyperfade";
import { createMonotonicNow } from "hyperfade/lib/time.js";
const { ctx, auto } = createHyperdriveEphemeralGC(drive, {
prefix: "/sessions",
filesToDelete: ["meta.json", "audio.m4a", "payload.txt"],
intervalMs: 5000, // Check every 5 seconds
});
// Create session that expires in 2 seconds
const now = createMonotonicNow();
const meta = {
id: "session-1",
createdAt: now(),
updatedAt: now(),
expiresAt: now() + 2000,
};
await ctx.saveMeta(meta);
await drive.put("/sessions/session-1/payload.txt", Buffer.from("Hello!"));
// Start auto-GC
auto.start();
// After 2+ seconds, files will be automatically deletedSee examples/visible.js for a complete example of content that unlocks after a set time.
import { createHyperdriveEphemeralContext, isVisible } from "hyperfade";
import { createMonotonicNow } from "hyperfade/lib/time.js";
const ctx = createHyperdriveEphemeralContext(drive, {
prefix: "/sessions",
metaFile: "meta.json",
filesToDelete: [], // Not using deletion in this example
});
// Create session that unlocks in 1 second
const now = createMonotonicNow();
const meta = {
id: "session-1",
createdAt: now(),
updatedAt: now(),
unlockAt: now() + 1000,
};
await ctx.saveMeta(meta);
await drive.put(
"/sessions/session-1/message.txt",
Buffer.from("Secret message!")
);
// Check visibility
const currentTime = now();
if (isVisible(meta, currentTime)) {
// Content is visible
} else {
// Content is locked - wait for unlockAt
}Checks if a meta object has expired.
- Parameters:
meta(EphemeralMeta): Meta object to checknow(number): Current timestamp (usecreateMonotonicNow()())
- Returns:
boolean-trueif expired,falseotherwise - Security: Validates timestamps to prevent bypass attacks
Checks if a meta object is unlocked.
- Parameters:
meta(EphemeralMeta): Meta object to checknow(number): Current timestamp (usecreateMonotonicNow()())
- Returns:
boolean-trueif unlocked,falseotherwise - Security: Validates timestamps to prevent bypass attacks
Checks if a meta object is visible (unlocked AND not expired).
- Parameters:
meta(EphemeralMeta): Meta object to checknow(number): Current timestamp (usecreateMonotonicNow()())
- Returns:
boolean-trueif visible,falseotherwise
Runs garbage collection manually.
- Parameters:
ctx(EphemeralGCContext): GC contextoptions.nowFn(optional): Clock function (defaults tocreateMonotonicNow())
- Returns:
Promise<{ expired: number }>
Creates an automatic GC controller.
- Parameters:
ctx(EphemeralGCContext): GC contextoptions.intervalMs(optional): Interval in milliseconds (default: 60000)options.nowFn(optional): Clock function (defaults tocreateMonotonicNow())
- Returns:
{ start(), stop(), isRunning() }
Creates a GC context for Hyperdrive.
- Parameters:
drive: Hyperdrive instanceoptions.prefix(string, required): Directory prefixoptions.metaFile(string, optional): Meta file name (default:'meta.json')options.filesToDelete(string[], required): Files to delete on expiration
- Returns:
EphemeralGCContextwithlistMetas(),saveMeta(meta),onExpire(meta)
Complete setup with convenience methods.
- Parameters: Same as
createHyperdriveEphemeralContextplus:options.intervalMs(number, optional): Auto-GC interval (default: 60000)
- Returns:
{ ctx, runOnce(), auto }
Creates a tamper-resistant clock function.
- Parameters:
options.maxBackwardsMs(optional): Tolerance for backward jumps (default: 1000ms)options.maxForwardMs(optional): Max forward jump allowed (default: 1 hour)
- Returns:
() => number- Clock function that never goes backwards - Security: Prevents clock manipulation attacks
import { createMonotonicNow } from "hyperfade/lib/time.js";
const now = createMonotonicNow();
const currentTime = now(); // Always increasing, tamper-resistantinterface EphemeralMeta {
id: string; // Required: Unique identifier (alphanumeric, hyphens, underscores only)
createdAt: number; // Required: Creation timestamp (ms)
updatedAt: number; // Required: Last update timestamp (ms)
unlockAt?: number; // Optional: Unlock timestamp (ms)
expiresAt?: number; // Optional: Expiration timestamp (ms)
}Security Constraints:
id: Max 255 chars, must match/^[a-zA-Z0-9_-]+$/- All timestamps: Must be finite numbers within reasonable bounds (0 to 100 years in future)
- Invalid data is rejected during
saveMeta()and filtered duringlistMetas()
All file paths are validated and sanitized. Invalid characters (.., /, \) are rejected entirely.
// ✅ Valid
await ctx.saveMeta({ id: 'session-123', ... })
// ❌ Invalid - will throw error
await ctx.saveMeta({ id: '../etc/passwd', ... })The library uses a monotonic clock that never goes backwards, preventing attackers from manipulating system time to bypass expiration.
// Always use createMonotonicNow() for security
const now = createMonotonicNow();
const currentTime = now(); // Protected against clock manipulationAll metadata is validated before processing:
- ID format: Alphanumeric, hyphens, underscores only
- Timestamps: Must be finite numbers within reasonable bounds
- File sizes: Meta files limited to 5MB
- Structure: Required fields validated, invalid data rejected
Files are verified to be actually deleted after expiration, with retry logic for edge cases.
- Always use
createMonotonicNow()for timestamps in production - Validate user input before creating meta objects
- Use
ctx.saveMeta()instead of writing meta.json directly (includes validation) - Check
isVisible()before reading content - Handle errors from
saveMeta()(validation errors are thrown) - Monitor logs for security warnings (invalid data, clock jumps, etc.)
try {
await ctx.saveMeta(meta);
} catch (err) {
if (err.message.includes("Invalid meta.id")) {
// Handle invalid ID format
} else if (err.message.includes("Invalid metaFile")) {
// Handle invalid file name
} else {
// Handle other errors
}
}- Expired data is deleted locally
- Other peers may still have the data
- Peers can re-replicate expired data back
- Consider using Hyperdrive access controls for additional protection
MIT