/**
 * HMAC-signed capability token that lets a SANDBOXED artifact iframe load the
 * workspace images it embeds.
 *
 * Why this exists: artifact iframes run with `sandbox="allow-scripts"` (no
 * `allow-same-origin`) → opaque origin → the `__sg_token` session cookie is
 * NOT sent on `<img>`/CSS asset requests, and an `<img>` can't carry a Bearer
 * header. So authorized image loads need the credential IN THE URL (`?at=`).
 *
 * Security shape (see ~/.claude/plans/artifact-asset-proxy.md):
 *  - **Artifact-scoped allowlist** — the token names the EXACT `assetId/filename`
 *    keys the artifact references (`assets`), not the whole repo. Malicious
 *    artifact JS holding the token can only re-load the images already on the
 *    page, not enumerate the repo.
 *  - **Bound user** — `userId` is re-checked at serve time (`hasRepoAccess`), so
 *    a token stops working the moment the user loses access (modulo browser
 *    cache; the serve path uses revalidate+ETag, not immutable).
 *  - **Purpose-pinned** — verify REJECTS any JWT that isn't `purpose:
 *    'artifact-asset'`, so a session/OAuth JWT signed with the same secret can
 *    never satisfy this gate (and this token can never satisfy a session gate).
 *
 * Signed with the workspace JWT secret via `authService.getJwtSecret()` — one
 * rotation lever, same as `asset-upload-token.ts`.
 *
 * The token is a bearer credential visible to (untrusted) artifact JS. The
 * defenses above (narrow asset allowlist + serve-time access re-check + short
 * TTL) — NOT `connect-src 'none'` — are what make that acceptable: artifact JS
 * can `postMessage` the token out, but it only unlocks this artifact's own
 * images for a user who still has access.
 */
/** Thrown by `verifyArtifactAssetToken` on tamper / expiry / wrong secret /
 *  wrong purpose / malformed shape. Callers map it to a fail-closed 404. */
export declare class ArtifactAssetTokenError extends Error {
    readonly code = "artifact_asset_token_invalid";
    constructor(message: string);
}
export interface ArtifactAssetTokenPayload {
    /** Discriminator. Verify rejects any token without exactly this value, which
     *  is what keeps session/OAuth JWTs (signed with the same secret) out. */
    purpose: 'artifact-asset';
    /** The repo the artifact lives in. Serve re-checks `payload.repo === row.repo`. */
    repo: string;
    /** The artifact entity id this token was minted for (audit + scoping context). */
    artifactId: string;
    /** Allowlist of exact `${assetId}/${filename}` keys the artifact references.
     *  Serve refuses any key not in this set — the token can't read sibling or
     *  unrelated assets even within the same repo. */
    assets: string[];
    /** Auth user id (as string) the token was minted for. Re-resolved + re-authed
     *  at serve time so revocation/removal takes effect before expiry. */
    userId: string;
}
export declare const ARTIFACT_ASSET_TOKEN_TTL_SECONDS: number;
export declare function signArtifactAssetToken(payload: ArtifactAssetTokenPayload, secret: string): string;
/**
 * Verify a token and return its payload. Throws `ArtifactAssetTokenError` on:
 *   - tamper (any field mutated) / wrong secret (rotation) / expired / malformed
 *   - wrong or missing `purpose` (rejects session + OAuth JWTs decisively)
 *
 * The caller (asset route, token path) is responsible for the post-verify
 * checks the signature alone can't make:
 *   - `payload.repo === row.repo`
 *   - `${row.assetId}/${row.filename}` ∈ `payload.assets`
 *   - re-resolve `payload.userId` and re-run `hasRepoAccess`
 */
export declare function verifyArtifactAssetToken(token: string, secret: string): ArtifactAssetTokenPayload;
/** Build the canonical allowlist key for an asset. Centralized so the mint,
 *  serve, and tests can't drift on separator/whitespace. */
export declare function artifactAssetKey(assetId: string, filename: string): string;
