import type { SessionSecretStore } from './session-secret-store.js'; /** * Sentinel request header a client sets to ask the fetch proxy to sign the * request body: `x-slicc-hmac-sign: :`. The proxy * computes `HMAC-SHA256(body, secretName's real value)`, attaches the hex * result under `targetHeader`, and strips this header before forwarding — * see `SecretsPipeline.signHmac`. * * An optional third segment, `::`, * switches to timestamp-bound signing: the MAC covers `.` * instead of the raw body, and the proxy also attaches the unix-seconds * timestamp it signed with under `timestampHeader`. This is what a receiver * needs to enforce a replay window (reject requests whose timestamp is too * far from "now") — a bare body digest has no way to do that, since the same * signed request stays valid forever. */ export declare const HMAC_SIGN_HEADER = "x-slicc-hmac-sign"; export interface FetchProxySecretSource { get(name: string): Promise; listAll(): Promise<{ name: string; value: string; domains: string[]; }[]>; } export interface MaskedSecret { name: string; realValue: string; maskedValue: string; domains: string[]; } export interface ForbiddenInfo { secretName: string; hostname: string; } export interface UnmaskResult { text: string; forbidden?: ForbiddenInfo; } export interface UnmaskHeadersResult { forbidden?: ForbiddenInfo; } export interface HmacSignResult { /** Header the computed signature should be attached under. Absent when `spec` was malformed or named an unknown secret — callers should leave the request unsigned in that case. */ headerName?: string; signatureHex?: string; /** Header the signed timestamp should be attached under. Present only when `spec` used the 3-segment timestamp-bound form. */ timestampHeaderName?: string; /** Unix-seconds timestamp folded into the signed message as `.`. Present only alongside `timestampHeaderName`. */ timestampValue?: string; forbidden?: ForbiddenInfo; } export interface BasicResult { value: string; forbidden?: ForbiddenInfo; } export interface ExtractedUrlCreds { url: string; syntheticAuthorization?: string; forbidden?: ForbiddenInfo; } export interface SecretsPipelineOpts { sessionId: string; source: FetchProxySecretSource; /** * Optional in-memory session-secret store. Its entries are layered on top * of `source` at every `reload()` so the fetch proxy can unmask session * secrets like persisted ones. Persisted secrets win on a name collision * (the agent cannot shadow a real persisted secret's masking). */ sessionStore?: SessionSecretStore; } /** * Stateful unmask/scrub pipeline shared between node-server's /api/fetch-proxy * and the chrome-extension SW's fetch-proxy.fetch Port handler. * * Public surface has four method families: * * ┌────────────┬────────────────────────────────┬─────────────────────────┐ * │ │ Text-safe (string in / out) │ Byte-safe (Uint8Array) │ * ├────────────┼────────────────────────────────┼─────────────────────────┤ * │ Unmask │ unmask, unmaskBody, │ unmaskBodyBytes │ * │ (mask→real)│ unmaskHeaders, …Basic, …Url │ │ * ├────────────┼────────────────────────────────┼─────────────────────────┤ * │ Scrub │ scrubResponse, scrubHeaders │ scrubResponseBytes │ * │ (real→mask)│ │ │ * └────────────┴────────────────────────────────┴─────────────────────────┘ * * Use the byte-safe variants for request/response bodies that may be binary * (git packfiles, ZIPs, images, application/octet-stream). The text variants * UTF-8-decode their input, which corrupts non-UTF-8 byte sequences * (`Buffer.toString('utf-8')` replaces invalid bytes with U+FFFD). * * Note: unmaskHeaders MUTATES its input in place (matching SecretProxyManager's * legacy semantics). The other methods return new strings/byte arrays. */ export declare class SecretsPipeline { readonly sessionId: string; private readonly source; private readonly sessionStore?; private maskedToSecret; /** Ordered array of maskable secret pairs for export redaction. Order is stable within a reload cycle. */ private exportPairs; /** * Ordered array of short (below MIN_MASKABLE_SECRET_LENGTH) secret pairs for export redaction. * Short secrets cannot be safely masked (identity masking), so only their real value is * replaced during export. Indices continue from where exportPairs ends. */ private exportShortPairs; private consumableShortSecrets; private byName; private scrubber; constructor(opts: SecretsPipelineOpts); reload(): Promise; maskOne(name: string, value: string): Promise; hasSecrets(): boolean; getMaskedEntries(): Array<{ name: string; maskedValue: string; domains: string[]; }>; /** * Unmask a single string. Domain mismatch on a matched secret → forbidden. * Returns { text } on success, { text: original, forbidden } on block. */ unmask(text: string, hostname: string): UnmaskResult; /** * Unmask body text. Domain mismatch on a matched secret leaves it untouched * (NO forbidden — masked values in conversation context are harmless). */ unmaskBody(text: string, hostname: string): { text: string; }; unmaskAuthorizationBasic(headerValue: string, hostname: string): BasicResult; extractAndUnmaskUrlCredentials(rawUrl: string): ExtractedUrlCreds; /** * Unmask headers IN PLACE. Mutates the headers parameter; returns only { forbidden? }. * Match SecretProxyManager's existing semantics so call sites compile unchanged. */ unmaskHeaders(headers: Record, hostname: string): UnmaskHeadersResult; /** * Resolve an `x-slicc-hmac-sign: :[:]` * directive against the (already-unmasked) request body. The real secret * value is looked up by name, domain-checked exactly like `unmaskHeaders`, * and used to compute the MAC — the caller attaches the hex result under * `targetHeader` and forwards. The real value never leaves this method. * * Two-segment specs (no `timestampHeader`) sign the raw body, unchanged * from the original behavior. Three-segment specs sign * `.` instead and additionally return `timestampValue` * for the caller to attach under `timestampHeaderName`, so the receiver can * enforce a replay window. `now` is injectable for tests; defaults to the * real clock. * * Returns `{}` (no-op) for a malformed spec or an unknown secret name — * the fetch proxy is expected to treat that as "nothing to sign", not a * hard error, since the header may have been set for a different purpose. */ signHmac(spec: string, body: Uint8Array, hostname: string, now?: () => number): Promise; unmaskBodyBytes(body: Uint8Array, hostname: string): { bytes: Uint8Array; }; scrubResponse(text: string): string; scrubResponseBytes(bytes: Uint8Array): Uint8Array; scrubHeaders(headers: Headers): Record; /** * Batch-redact an array of strings for transcript export. * * Replaces every occurrence of each known secret's real value AND masked * value with a stable anonymous marker `⟦REDACTED:known-secret:k⟧`. * The index `n` is 1-based and stable within a single reload cycle. * * Returns the transformed texts plus the total number of replacements made. * Secret names and real values never appear in the return value. */ redactForExport(texts: readonly string[]): { texts: string[]; redactionCount: number; }; } //# sourceMappingURL=secrets-pipeline.d.ts.map