Skip to content

feat(cli): support https URL values for --fixtures - #128

Merged
jpr5 merged 5 commits into
mainfrom
feat/fixtures-remote-url
Apr 23, 2026
Merged

feat(cli): support https URL values for --fixtures#128
jpr5 merged 5 commits into
mainfrom
feat/fixtures-remote-url

Conversation

@jpr5

@jpr5 jpr5 commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Remote https:// / http:// URLs for --fixtures with on-disk cache + fail-loud recovery
  • SSRF hardening: private-address denylist (AIMOCK_ALLOW_PRIVATE_URLS=1 opt-out for local dev)
  • Redirects rejected fail-loud (configure upstream to serve the final URL directly)
  • 10s hard timeout + 50MB incremental body cap (lying Content-Length cannot bypass)
  • --fixtures is now repeatable; argv order preserved

Why

Phase 1 of showcase-aimock wrapper elimination — Railway can pull fixtures from stable GitHub raw
URLs instead of rebuilding an image per fixture update.

Test plan

  • 30+ new tests across unit + integration + CLI subprocess layers; 2520/2520 suite-wide pass
  • Real sockets (no mocks) for every fetch path
  • Coverage: timeout, incremental cap (chunked + lying CL), cache fallback, scheme rejection,
    redirect rejection, SSRF denylist (table-driven across 20 reserved ranges + 4 public IPs),
    env opt-out, invalid JSON fail-loud, non-zero exit, repeatable --fixtures load order

@jpr5
jpr5 requested a review from AlemTuzlak April 22, 2026 22:25
@pkg-pr-new

pkg-pr-new Bot commented Apr 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@copilotkit/aimock@128

commit: 385b6d4

@jpr5 jpr5 changed the title feat(cli): support https URL values for --fixtures (v1.14.6) feat(cli): support https URL values for --fixtures Apr 22, 2026
@jpr5
jpr5 force-pushed the feat/fixtures-remote-url branch from 5c09085 to 8d45701 Compare April 23, 2026 00:21
jpr5 added 3 commits April 22, 2026 17:43
Teach --fixtures to accept https:// or http:// URLs pointing at JSON
fixture files in addition to filesystem paths. Remote values are
fetched once at boot, parsed, and cached to disk so downstream code
treats them identically to a local file.

- New src/fixtures-remote.ts helper: resolveFixturesValue(value, opts)
  returns a local filesystem path regardless of input scheme.
- Cache at $XDG_CACHE_HOME/aimock/fixtures/<sha256-of-url>/fixtures.json
  (default ~/.cache/aimock/fixtures/<sha256>/fixtures.json).
- Under --validate-on-load, a fetch failure with a usable cached copy
  logs a warning and continues; no cache -> process exits non-zero.
- Without --validate-on-load, a fetch failure with no cache logs a
  warning and skips the source (matching the local-path branch).
- HTTP fetch has a 10s timeout and a 50 MB size cap, both enforced
  incrementally so a lying Content-Length can't bypass the limit.
- Only https:// and http:// schemes are accepted. file://, ftp://,
  etc. are rejected with a clear error.
- --fixtures is now repeatable; multiple sources are loaded and
  concatenated. Record / AG-UI record modes still require a local
  filesystem path (use the first --fixtures value).

Tarball (.tar.gz) and zip URL support is intentionally deferred to a
later release to keep this change small and dependency-free.
…tures

Harden --fixtures URL fetching against two SSRF classes that v1.14.7
left open:

1. Private / reserved destinations.  Fetching http://169.254.169.254/
   (AWS/GCP/Azure instance metadata), RFC1918, CGNAT, loopback, link-
   local, ULA, multicast, and other reserved ranges now fails loud
   with a clear error.  Set AIMOCK_ALLOW_PRIVATE_URLS=1 to opt out —
   required for local dev and tests that target 127.0.0.1.  Hostname
   resolution checks every returned address; any blocked entry in
   the set rejects the fetch.

2. Redirect follow.  The fetch previously used the default redirect:
   "follow" behavior, which would silently chase a 3xx Location into
   a different scheme (file://, javascript:) or host — bypassing the
   scheme gate and the new SSRF denylist.  The fetch now uses
   redirect: "manual" and rejects any 3xx with a fail-loud error
   telling the user to configure the upstream to serve the final URL
   directly (GitHub raw content URLs already do this; a CDN that
   insists on redirecting is incompatible with a fixture loader).

Implementation:

- New helpers in src/fixtures-remote.ts: isPrivateAddress(addr) and
  assertAllowedHost(hostname).  Both are exported for direct testing.
- Uses net.BlockList (Node >= 15) for CIDR membership checks rather
  than hand-rolled integer math.  v4-mapped IPv6 (::ffff:a.b.c.d) is
  unwrapped and re-checked against the v4 ranges.
- SSRF check runs before any network I/O; the assertion throws a
  clear error before DNS is even consulted for literal-IP hostnames.
Pins behavior across the remote-fixture feature and its security
hardening layer with real-socket tests (no mocks):

src/__tests__/fixtures-remote.test.ts (unit + integration)
  - looksLikeUrl scheme recognition and local-path passthrough
  - defaultCacheRoot honors XDG_CACHE_HOME
  - http(s) success: fetch, cache to disk, return cached path
  - http(s) failure modes: 500 fail-loud, cache fallback, timeout,
    incremental body-cap enforcement, Content-Length early reject,
    invalid-JSON fail-loud
  - exported default constants: REMOTE_FETCH_TIMEOUT_MS == 10_000,
    REMOTE_MAX_BYTES == 50 * 1024 * 1024
  - lying Content-Length safety: chunked-encoding body past cap,
    truncation-safety fail-loud contract
  - SSRF denylist: table-driven isPrivateAddress across 20 reserved
    ranges + 4 public IPs; assertAllowedHost env opt-out +
    per-address rejection; integration: 169.254.169.254 and
    127.0.0.1 rejected without the opt-out, permitted with it
  - redirect rejection: 302 -> file:// and 302 -> https://other
    both fail loud under redirect: "manual"
  - file-wide beforeAll sets AIMOCK_ALLOW_PRIVATE_URLS=1 so the
    existing tests that fetch from 127.0.0.1 still pass

src/__tests__/cli.test.ts (CLI subprocess)
  - Remote --fixtures URL success, 500 + no-cache fail-loud,
    500 + cache fallback, file:// rejection, filesystem-path
    regression guard
  - Repeatable --fixtures flag with two local http servers:
    asserts both fixtures load (Loaded 2 fixture(s)) and argv
    order is preserved in the startup log line
  - beforeEach in the remote-URL describe block sets
    AIMOCK_ALLOW_PRIVATE_URLS=1 so subprocess fetches against
    127.0.0.1 aren't rejected by the new SSRF denylist
@jpr5
jpr5 force-pushed the feat/fixtures-remote-url branch from 8d45701 to a668f3a Compare April 23, 2026 00:47
jpr5 added 2 commits April 22, 2026 17:53
…-out

Adds a short example pair to the CLI block showing a repeatable HTTPS
--fixtures flag alongside a local path, plus a new 'Remote fixture URLs'
subsection covering the on-disk cache at ~/.cache/aimock/fixtures/,
--validate-on-load fallback behavior, the 10s timeout / 50 MB body cap /
redirect rejection, and the private-address denylist with the
AIMOCK_ALLOW_PRIVATE_URLS=1 opt-out for local dev.
Ships the remote-fixture feature plus its security hardening layer:
https/http URL values for --fixtures (fetch + on-disk cache + fail-
loud recovery, 10s timeout, 50 MB incremental body cap), an SSRF
denylist rejecting private / reserved addresses by default, and
disabled HTTP redirects to prevent scheme-bypass.  Opt out of the
SSRF denylist with AIMOCK_ALLOW_PRIVATE_URLS=1 for local dev.
@jpr5
jpr5 force-pushed the feat/fixtures-remote-url branch from a668f3a to 385b6d4 Compare April 23, 2026 00:53
@jpr5
jpr5 merged commit 5068ffc into main Apr 23, 2026
22 checks passed
@jpr5
jpr5 deleted the feat/fixtures-remote-url branch April 23, 2026 00:59
jpr5 added a commit that referenced this pull request Jul 15, 2026
## Summary

- Remote `https://` / `http://` URLs for `--fixtures` with on-disk cache
+ fail-loud recovery
- SSRF hardening: private-address denylist
(`AIMOCK_ALLOW_PRIVATE_URLS=1` opt-out for local dev)
- Redirects rejected fail-loud (configure upstream to serve the final
URL directly)
- 10s hard timeout + 50MB incremental body cap (lying `Content-Length`
cannot bypass)
- `--fixtures` is now repeatable; argv order preserved

## Why

Phase 1 of showcase-aimock wrapper elimination — Railway can pull
fixtures from stable GitHub raw
URLs instead of rebuilding an image per fixture update.

## Test plan

- 30+ new tests across unit + integration + CLI subprocess layers;
2520/2520 suite-wide pass
- Real sockets (no mocks) for every fetch path
- Coverage: timeout, incremental cap (chunked + lying CL), cache
fallback, scheme rejection,
redirect rejection, SSRF denylist (table-driven across 20 reserved
ranges + 4 public IPs),
env opt-out, invalid JSON fail-loud, non-zero exit, repeatable
`--fixtures` load order
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant