/** SigV4 physical ceiling (AWS: X-Amz-Expires max 604800s = 7 days). */ export declare const PRESIGN_MAX_TTL_SEC = 604800; /** * Multi-tenant governance (clay ruling 2026-07-14, blackboard [ref]② variant a with a HASHED segment): every * object key carries a per-tenant SCOPE SEGMENT so tenants can be listed/revoked by prefix on the store itself, * WITHOUT leaking the principal into the (user-visible, forwardable) URL. The segment is * `HMAC-SHA256(secretKey, "sendfile-scope:" + scope)` hex, truncated to 32 chars: * - keyed on the S3 SECRET KEY — server-held and stable, so the mapping is deterministic per deployment but * unforgeable/unenumerable from outside (a URL holder cannot test principal guesses without the key); * - the "sendfile-scope:" DOMAIN SEPARATOR pins this HMAC use — the same secret signs SigV4 requests, and a * cross-protocol collision between "scope segment" and any other HMAC-of-caller-controlled-string use is * exactly what domain separation exists to prevent; * - 32 hex chars (128 bits) is a NAMESPACE label, not a secret: the cross-tenant BIRTHDAY bound at even a * billion tenants is ~n²/2^129 ≈ 2^-69 — negligible. (The earlier 16-hex/64-bit cut had a ~2.7% birthday * collision probability at that scale — n²/2^65, NOT the per-pair 2^-32 the old note claimed; codex audit * F4, widened pre-release at zero cost.) Capability secrecy still lives in the uuidv7 segment. Key budget * stays comfortable: worst-case private-track key = 9 ("sendfile/") + 32 + 1 + 36 + 1 + 128 = 207 chars, * under both sendfile_link.object_key VARCHAR(512) and S3's 1024-byte key ceiling. * scope undefined/"" (single-user deployments have no principal) → the literal "_" sentinel (same sentinel the * checkpoint scope column uses for anonymous), so the key SHAPE is uniform across deployments. * ⚠️ Objects issued before 1.190.0 have NO scope segment (`/`); they stay valid — the segment is * purely additive (URLs are capability-complete either way), zero migration. */ export declare function scopeSegment(secretKey: string, scope: string | undefined): string; export interface SendUserFileConfig { /** Internal S3/MinIO endpoint the SERVER uploads through (adapter-held creds never leave the server). */ endpoint: string; /** Public base URL users reach (files. subdomain / NodePort / CDN). Absent ⇒ issue() fail-louds with the fix. */ publicEndpoint?: string; /** Anonymous-GET bucket for the permanent track. */ publicBucket: string; /** Private bucket for the presigned track. */ privateBucket: string; /** Key prefix inside the private bucket (keeps sendfile objects apart from snapshot blobs). */ privateKeyPrefix: string; /** v2: endpoint the SANDBOX uploads through (presigned PUT Host-binding). Default = publicEndpoint — * an e2b cloud sandbox can only reach the public face; an in-cluster k8s pod may prefer the internal * endpoint (SEND_USER_FILE_SANDBOX_PUT_ENDPOINT). */ sandboxPutEndpoint?: string; accessKey: string; secretKey: string; region?: string; /** Default ttl when the tool call omits it. 0 = permanent (the settled default). */ defaultTtlSec: number; } export interface IssuedFileLink { url: string; filename: string; size: number; /** 0 = permanent (public track). */ ttlSec: number; track: "public" | "presigned"; bucket: string; key: string; /** The ORIGINAL tenant scope this link was issued under (verbatim, for the server-side ledger — the KEY only * carries the hashed segment). Absent on single-user issuance (no principal). */ scope?: string; } /** * Collapse an arbitrary client-supplied name to ONE safe path segment: strip directories (both * separators), control/bidi characters (the session-title sanitize class), leading dots (no hidden * files / no "." ".."), and cap the length (S3 key segment budget; keep the extension when truncating). * Empty in ⇒ "file". */ export declare function sanitizeSendFileName(name: string | undefined): string; /** v2 (sandbox direct-upload, clay 2026-07-14): the two-phase face. `prepare` mints the FINAL object key * plus a short-lived single-object presigned PUT on a separate STAGING key (the sandbox `curl -T`s the * file itself — bytes never relay through the server, creds never enter the sandbox); `finalize` mints the * user-facing link for the FINAL key. The PUT URL is a capability secret scoped to one object+method+TTL — * callers must keep it out of model context and redact it from errors/logs (adapter posture, * remote-env-k8s redactPresigned). * * STAGING two-phase (修3, closes the 1.190 Plan B residual): the PUT URL rides the sandbox exec argv, so * any process in the sandbox can read it via /proc and re-PUT while it is valid (120s) — previously that * meant overwriting the FINAL object AFTER server-side verification, i.e. after the user got the link. * Now the sandbox can only ever write the staging key (`staging//` in the * PRIVATE bucket — never exposed as a public URL); the server CopyObjects it to the final random key with * its own creds (the sandbox never sees a write capability for that key), deletes the staging object, and * verifies size on the now-immutable final object. A late re-PUT of the expired-or-not staging URL can no * longer touch what the user's link points at. */ export interface PreparedDirectUpload { /** Presigned PUT the sandbox uploads through — the STAGING key (single object, method-bound, short TTL); * never the final key, which no sandbox-visible write capability ever exists for. */ putUrl: string; /** SERVER-side post-upload accounting (codex review: the in-sandbox stat is advisory — the sandbox owns * its filesystem and the presigned PUT is UNSIGNED-PAYLOAD, so nothing binds the uploaded byte count). * Runs the staging→final promotion with adapter creds via the internal endpoint: HEAD staging (missing ⇒ * throw — claimed upload didn't land; over-cap ⇒ DELETE staging, throw), CopyObject staging→final, DELETE * staging, then an authoritative HEAD of the FINAL object (whose size the card must carry — the copy is * what pins the bytes). 修2: EVERY failure past the object landing reclaims best-effort (staging and/or * final) before throwing — a transient HEAD 5xx must not leave an unledgered orphan. */ verifySize(maxBytes: number): Promise; /** Mint the user-facing link once the upload is verified. `size` = verifySize's reading. */ finalize(size: number): IssuedFileLink; } export interface SendUserFileIssuer { /** Upload bytes and mint the user-facing URL. Throws (fail-loud, typed message) on config/物理 limits. * `scope` = the caller's VERIFIED principal (multi-tenant); it keys the hashed scope segment in the object * key and rides back verbatim on IssuedFileLink.scope for the ledger. Omit on single-user. */ issue(bytes: Uint8Array, filename: string | undefined, ttlSec?: number, scope?: string): Promise; /** v2: mint a presigned PUT for a sandbox-side direct upload (no bytes through the server). Same `scope` * semantics as issue() — prepare and finalize share ONE key, so the segment is fixed at prepare time. */ prepareDirectUpload(filename: string | undefined, ttlSec?: number, scope?: string): PreparedDirectUpload; /** Best-effort object reclaim (ledger fail-loud path: an uploaded object whose ledger record failed would be * an unmanageable orphan — delete it). Signed DELETE against the INTERNAL endpoint with adapter creds. */ deleteObject(bucket: string, key: string): Promise; /** The public endpoint web clients need for rendering context (bootstrap/capabilities透出). */ readonly publicEndpoint: string | undefined; } export declare function createSendUserFileIssuer(cfg: SendUserFileConfig, deps?: { /** Injectable upload (tests). Default = SigV4 presigned PUT against the INTERNAL endpoint + fetch. */ upload?: (bucket: string, key: string, bytes: Uint8Array) => Promise; /** Injectable post-direct-upload accounting (tests). Default = HEAD (+ DELETE when over cap) against * the INTERNAL endpoint with adapter creds. Returns the object's real size. */ verify?: (bucket: string, key: string, maxBytes: number) => Promise; /** Injectable object delete (tests). Default = signed DELETE against the INTERNAL endpoint. */ remove?: (bucket: string, key: string) => Promise; now?: () => Date; /** C3(2026-08-03)测试座:默认实现的六个 S3 fetch 站点走此座——signal 在场性自此可钉。 */ fetchImpl?: typeof fetch; }): SendUserFileIssuer; //# sourceMappingURL=send-user-file.d.ts.map