witmproxy 0.0.1-alpha

A WASM-in-the-middle proxy
package witmproxy:plugin@0.0.5;

interface capabilities {
    use wasi:http/types@0.3.0-rc-2025-09-16.{request, response};

    // TODO:
    // resource http-client {} <- an HTTP client for making outbound requests
    // resource key-value-client {} <- a simple key-value store client
    // resource sql-client {} <- a SQLite/? database client
    // resource queue-client {} <- a mpsc queue
    // resource custom {} <- for user-defined capabilities

    /// A dumb and simple logging resource (for writing to stdout/stderr)
    /// TODO: replace with official wasi-logging when available
    resource logger {
        info: func(message: string);
        warn: func(message: string);
        error: func(message: string);
        debug: func(message: string);
    }

    /// A resource for annotating/labeling content
    resource annotator-client {
        /// Annotate the provided content (extract and store features + metadata)
        annotate: func(content: content);
    }

    /// A resource for storing key-value pairs local to the plugin (sandboxed and inaccessible elsewhere)
    resource local-storage-client {
        set: func(key: string, value: list<u8>);
        get: func(key: string) -> option<list<u8>>;
        delete: func(key: string);
    }

    /// A capability provider, which only returns capabilities that have been granted by the user
    resource capability-provider {
        // http: func() -> option<http-client>;
        // kv: func() -> option<key-value-client>;
        // sql: func() -> option<sql-client>;
        // queue: func() -> option<queue-client>;
        logger: func() -> option<logger>;
        annotator: func() -> option<annotator-client>;
        local-storage: func() -> option<local-storage-client>;
    }

    /// A type used to limit the scope in which granted capabilities can be used.
    /// Used primarily to filter events before more expensive processing and overhead 
    /// (such as instantiating and executing WASM components), but can also be used to limit
    /// the context in which certain capabilities (like local storage) can be used.
    /// 
    /// Capability scopes can be configured by the user and may not necessarily match
    /// the requested scope from the plugin.
    record capability-scope {
        /// A CEL expression, evaluating to `true` or `false` (whether or not a given capability should be granted), matching against the context provided to the capability,
        /// determining whether the capability applies.
        /// 
        /// See [CelRequest], [CelResponse], and [CelContent] for the context available to each expression.
        /// 
        /// ```rs
        /// fn evaluate(request: CelRequest) -> bool { request.path() == "/example" } // for request events
        /// fn evaluate(response: CelResponse, request: CelRequest) -> bool { response.status() == 200 && request.path() == "/example" } // for response events
        /// fn evaluate(content: CelContent) -> bool { content.content_type() == "text/html" } // for inbound-content events
        /// ```
        expression: string,
    }

    /// The different kinds of events that can be handled by plugins
    variant event-kind {
        /// The associated capability determines which connections should be intercepted by the proxy 
        /// 
        /// If you do not request this capability, you are not guaranteed to receive any further
        /// request/response events related to connections you wish to handle.
        connect,
        // The associated capability determines which HTTP requests should be handled by the plugin
        request,
        // The associated capability determines which HTTP responses should be handled by the plugin
        response,
        // The associated capability determines which inbound content should be handled by the plugin
        inbound-content,
    }

    /// The different kinds of capabilities that can be requested by plugins
    variant capability-kind {
        /// A capability to handle specific events
        handle-event(event-kind),
        /// A capability to log messages
        logger,
        /// A capability to annotate content (extract and store features + metadata) 
        annotator,
        /// A capability to store local key-value pairs
        local-storage,
    }

    /// A capability requested by the plugin
    record capability {
        /// The kind of capability being requested
        kind: capability-kind,
        /// The scope in which the capability applies (may be modified by the user)
        scope: capability-scope,
    }

    type keyed-values = tuple<string, list<string>>;
    type request-query = keyed-values;
    type request-header = keyed-values;

    /// The context of an HTTP request (essentially, everything except the body)
    record request-context {
        scheme: string,
        host: string,
        path: string,
        query: list<request-query>,
        method: string,
        headers: list<request-header>,
    }

    /// A response along with its associated request context
    record contextual-response {
        /// The HTTP response
        response: response,
        // TODO: turn request-context into a resource to avoid copying and modifications
        request: request-context,
    }

    /// The different types of events that can be handled (and returned) by plugins
    variant event {
        request(request),
        response(contextual-response),
        inbound-content(content),
    }

    /// A work-in-progress resource representing abstract byte stream content
    /// Primarily exists to hide compression of the underlying body, 
    /// providing a simple interface for plugins to interact with 
    /// without having to worry about compression details 
    /// (which could be wasteful or conflicting in the case of serial plugin execution).
    /// 
    /// Still requires handling content encodings (UTF-8, UTF-16, etc) manually.
    resource content {
        /// Returns the content as a stream of bytes
        body: func() -> stream<u8>;
        /// Returns the content type of the content, ex: "text/html; charset=utf-8"
        content-type: func() -> string;
        /// Replace the content body with the provided stream of bytes
        set-body: func(content: stream<u8>);
    }
}

interface witm-plugin {
    use capabilities.{capability, capability-provider, event};

    /// A tag representing a key-value pair of metadata
    record tag {
        key: string,
        value: string,
    }

    /// A manifest describing the plugin, any requested capabilities, and metadata.
    record plugin-manifest {
        /// The name of the plugin
        name: string,
        /// The plugin author's namespace (use for scoping)
        namespace: string,
        /// The publishing plugin author
        author: string,
        /// The plugin version
        version: string,
        /// A short description of the plugin
        description: string,
        /// The plugin license
        license: string,
        /// The plugin homepage URL
        url: string,
        /// The plugin author's public key (for verifying the plugin)
        publickey: list<u8>,
        /// The capabilities requested by the plugin
        capabilities: list<capability>,
        /// Additional plugin metadata
        metadata: list<tag>,
    }

    /// A function called by the host to retrieve the plugins manifest.
    /// 
    /// Used by the host to fetch (and verify) all necessary information about the plugin,
    /// including requested capabilities, before instantiating and executing it.
    /// 
    /// The public key will be used by the host to verify the WASM components signature,
    /// ensuring the plugin has not been tampered with.
    /// 
    /// If the manifest is invalid or cannot be verified, the host will not load the plugin.
    manifest: func() -> plugin-manifest;

    /// A function called by the host to handle events granted by the plugin's given capabilities.
    /// 
    /// The capability provider can be used to access additional host capabilities (for example, logging or local storage) within the plugin.
    /// Because requested capabilities may not necessarily be granted, the plugin is responsible for checking and handling whether a capability they require is available.
    /// 
    /// The handler may return the event as is, perform modifications, return a new event entirely (so long as the event output is valid given the input),
    /// or return `None` to abandon any further event processing (likely resulting in an HTTP error from the perspective of the host).
    /// 
    /// * [Request] events must return either [Request] or [Response] events.
    /// * [Response] events must return [Response] events.
    /// * [InboundContent] events must return [InboundContent] events.
    handle: func(ev: event, cp: capability-provider) -> option<event>;
}

world plugin {
    import capabilities;
    export witm-plugin;
}