Skip to content

Releases: coo-quack/vigilant-heron

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 28 Oct 18:52
85d4110
  • Added: the site directive static_query_params <name> ...; lets a static asset
    request with a cache-busting query, such as WordPress's ?ver=6.8.3, skip the
    backend. The query must be made only of the listed parameters, each with a plain
    value of 1 to 64 letters, digits, ., _ or -. Before, any query sent the
    request to the backend, so a page view sent every such asset there and could hit
    max_concurrency_per_client. Unset, the static skip still requires no query.

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 27 Oct 19:40
5e522d7
  • Changed: a prefix location and a ^~ location with the same path in one site
    are now rejected at load as a duplicate, as nginx rejects them. Before, both were
    accepted and the first one written was used. A configuration that has such a pair
    must keep one of them. An = location may still share its path with a prefix
    location.

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 27 Oct 00:25
d727884
  • Added: check_request_with, the library form of check_request that takes an
    already loaded Config, so a caller that checks many requests loads the
    configuration once.
  • Added: the $query_shape log variable and a query_shape field in the built-in
    json format. It records the names of the query parameters and the kind of each
    value (p=<num>&s=<text>), never the values, so a log shows which kinds of
    requests a site receives. See Logging.
  • Added: vigilant-heron suggest-skip-rules <access-log> reads a json access log
    and suggests skip rules for request shapes that the model judged harmless many
    times, from many clients, over several days, and never blocked. It only prints the
    rules for review. See
    Learn skip rules from the access log.
  • Added: each GitHub Release has Linux binaries for x86_64 and aarch64 (glibc 2.39
    or newer) and a SHA256SUMS file. See
    Download a release binary.
  • Fixed: docker build -t vigilant-heron . without --target built the e2e stub
    decision backend instead of the router. The router is now the Dockerfile's last
    stage.
  • Fixed: a request path with a NUL byte could select a different location for
    the block response, the headers and the WebSocket tunnel than for the limits,
    because the decoded path kept the NUL byte and the other readings did not. NUL
    bytes are now removed from every reading, so /%00admin selects the same
    location everywhere.
  • Fixed: the suggest-skip-rules report now says that a learned rule forwards
    matching requests without the model, so the model no longer judges their
    headers or body. Block rules, built-in and your own, still run first.

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 26 Oct 09:56
39d066d
  • Added: vigilant-heron test-request <file> [--client <address>] reports what the
    router would do with one raw HTTP/1.1 request before deployment: the site, the access
    list, deny_path, the rules that match, and whether the request would go to the
    decision backend. It runs the server's own checks, never calls the upstream or the
    backend, and does not simulate limit_req or limit_conn counts. See
    Check a request with test-request.

  • Fixed: a stale pid file could make -s, stop, restart and start act on an
    unrelated process. When a router died without removing its pid file and the OS
    gave its pid to another process, -s stop or stop signalled that process, and
    start refused with "already running". A running router now holds an exclusive
    lock on <pid file>.lock, and those commands treat a pid file as running only
    while the lock is held. A stale file is never signalled. Also, two routers with
    the same pid path no longer overwrite each other's pid file: the second exits
    with another router is using the pid file <path>.

  • Changed: releases now run the tests and the advisory check (cargo deny and
    pnpm audit) in a job without write access, and the publishing job keeps no
    credential in its checkout. The version tag is created through the REST API.

  • Fixed: the opt-in virtual patches vh-cve-2026-9103 (/api/v1/login/auto_login)
    and vh-cve-2026-8481 (/api/v1/validate/code) compared the path case-sensitively,
    so a request such as /API/V1/LOGIN/AUTO_LOGIN got past them. Both rules now match
    the path in any letter case. Which requests they block is otherwise unchanged.

  • Security: more credentials are kept out of the decision backend. Headers named
    with signature (for example x-hub-signature-256, svix-signature and
    x-slack-signature), with ticket or authentication as a word, or ending in
    key, authkey or sessionkey (for example x-functions-key, x-master-key
    and x-admin-key) are now dropped. Body fields with signature in their name are
    redacted. The rules for ticket, authentication
    and the key suffix apply to header names only, so an ordinary key or ticket
    body or query field is still sent. Multipart parts whose name is sensitive have
    their values replaced, and XML elements such as <password> and <wsse:Password>
    have their text replaced. Form bodies split on ; as well as &, so
    a=1;password=x hides the password. A file part is redacted by its field name
    only. Its content is not scanned.

  • Fixed: a proxy_websocket tunnel was read as if it were HTTP requests. After the
    101 Switching Protocols, a tunnelled chunk of more than 8 KiB without a line
    break (a binary WebSocket frame, for example) was answered with 414 and the
    connection was closed, and a tunnel open longer than client_header_timeout
    (60s by default) received a 408 while it was still active. Tunnelled bytes are
    now copied as they arrive, with no header limits and no header timer.

  • Fixed: a zone keyed on a long client-chosen value, such as a request header,
    could use far more memory than its declared size, because each state stored the
    whole key. States now store a fixed-size digest of the key, so the declared size
    bounds the memory again.

  • Added: decision backend strands (decision.backend strands;) for a self-hosted
    Strands Decider server, which speaks the System One protocol. model defaults to
    strands-decider, url is required, and api_token_env is optional: without it,
    no Authorization header is sent.

  • Behaviour change: a client that holds its max_concurrency_per_client share of
    decision calls is now answered 429 with an empty body, and the backend is not
    called. Before, that excess followed on_rate_limit, which defaults to allow,
    so it was forwarded without the model. The new site directive on_client_busy allow|block; (default block) controls it, and on_rate_limit no longer
    affects it. Set on_client_busy allow to keep the old forwarding. Shadow mode
    records the 429 as the would-have action. The decision log reason is
    client_busy_allowed or client_busy_blocked.

  • The query string sent to the decision backend is redacted. A parameter whose
    decoded name is sensitive, by the same rule as body fields, or named in
    redact_field, has its value replaced by [REDACTED]. Other parameters are sent
    as the client sent them. ; separates parameters as & does, so
    password=x;id=1 hides the password and sends id=1.

  • Security (F2): short sensitive parts no longer match inside ordinary names.
    ssn, card, cvv and pwd now match only as whole words, so class_name,
    business_name, process_name, address_number and discard are sent to the
    decision backend unredacted. card_id, user_ssn, userSSN, cvv and new_pwd
    are still redacted. creditcard and cardnumber match as parts of a name.

  • Security (F1): the commands that signal the router (-s, start, stop and
    restart) refuse a pid file in a directory that other users can write, such as
    /tmp, when another user owns the file. A pid that is not a positive decimal
    number, such as 0 or -1, is refused with an error. Before, -1 could signal
    every process of the user. The host guide now writes the pid file to a relative
    path, pid vigilant-heron.pid;, which is resolved from the directory the command
    runs in.

  • Headers with a credential-like name are no longer sent to the decision backend,
    for example x-csrf-token, x-xsrf-token, x-amz-security-token,
    x-access-token and x-session-id. host, user-agent, accept, referer,
    origin, x-forwarded-for and the other headers the model uses are still sent.

  • A configuration reload keeps the decision slots. Calls admitted before a reload
    still count against max_concurrency and max_concurrency_per_client, which a
    reload used to exceed. The slots are reused while their limit is unchanged. A
    changed limit gets new slots, and calls in flight keep the slots they hold.

  • start no longer signals a process it has already reaped when the router exits
    while starting. The pid of a reaped process can belong to another process.

  • The pid file's temporary file and the startup log are created exclusively. A file
    planted at those paths, such as a symlink in a shared directory like /tmp, is
    no longer written through.

  • The docs said that a request whose Host matches no site is always 403. It gets
    default_status (421 by default) with reason unknown_host, unless the top-level
    access list denies it. The docs now say so. The security notes also used the
    wrong name fail_closed_paths; the directive is fail_closed_path.

  • stop waits worker_shutdown_timeout plus 5 seconds, or 35 seconds when the
    directive is not set. The docs now give that figure.

  • start, stop and restart run the router in the background on a host. start
    checks the file first, refuses when a router already runs, and prints
    started (pid N). stop sends the graceful shutdown and waits until the process
    has exited, and prints not running when none runs. restart checks the file
    first, so an invalid file leaves the running router alone. Under Docker or
    systemd, run the router in the foreground, as before.

  • The shutdown signals (SIGINT, SIGTERM, SIGQUIT) are registered before the pid
    file is written. A stop sent right after the pid file appears now runs the
    cleanup that removes it, where it used to end the process without that cleanup.

  • docs/install.md builds from source and from the Dockerfile. It no longer
    mentions a published image, and it has a systemd unit example.

  • SECURITY.md lists the reporting channels: email and a GitHub security advisory.

  • Command line, as nginx's: -h/--help and -v/--version, -c <path> as a
    short form of --config, -T (test, then print the configuration with every
    include expanded under its # configuration file <path>: header), and
    -s reload|reopen|stop|quit. -s sends SIGHUP, SIGUSR1, SIGTERM or SIGQUIT to
    the pid in the file the pid directive names. An unknown option prints the
    usage and exits 1. stop and quit are both graceful in this version.

  • pid <path>; directive (default /run/vigilant-heron.pid). The router writes its
    process id there once it is listening and removes it on exit. A write failure is
    logged and the router keeps running. SIGQUIT now shuts down gracefully, like
    SIGTERM and SIGINT.

  • docs/cli.md describes the command line, and pid is documented in
    docs/configuration.md. The migration notes from the README moved to
    docs/configuration.md.

  • README rewritten as a short introduction with install, a quick start, the
    command line, the order of checks, and links to the documentation. The
    reference stays in docs/.

  • Removed rustfmt.toml, which only repeated edition = "2024" from Cargo.toml.
    cargo fmt output is unchanged.

  • enable_rule <id> ...; at the top level and in a site turns on built-in rules
    that are off by default. A built-in rule written with default off; runs only
    where it is enabled. The nearest level that names a rule decides, a site's lists
    before the top level's, and disable_rule wins over enable_rule within one level.
    default off; is valid only in the built-in rules.

  • Opt-in virtual patches vh-cve-2026-9103 (IBM Langflow OSS
    /api/v1/login/auto_login, CVE-2026-9103) and vh-cve-2026-8481 (IBM Langflow OSS
    POST /api/v1/validate/code, CVE-2026-8481). Both are off by default. IBM's fix
    for CVE-2026-9103 is Langflow 1.10.1; for CVE-2026-8481, upgrade past 1.10.0 and
    check the bulletin. The docs page "From CVE advisories to rules" describes how
    to write, roll out and remove such rules, and the policy for shipped ones.

  • Host matching as in nginx server_name: exact names, `*...

Read more