Repository navigation
Import REST tools from a HAR file (browser network capture) - #1057
Conversation
Lets an operator capture a web app's traffic in DevTools ("Save all as
HAR") and import it as REST tools when the API has no OpenAPI spec,
Postman collection or docs. Requests are matched to the connector's
base URL, static assets and WebSocket entries are skipped, and
same-template requests (e.g. /orders/123 and /orders/456) collapse
into one tool with an {id} parameter.
A HAR carries the browser's live session: cookies, bearer tokens and
API keys. The browser strips response bodies, cookies and
credential-looking headers before the file ever leaves it (with a
size check so an oversized capture is rejected in the panel instead
of failing the upload with a 413), and the backend parser drops
Cookie/Authorization/credential-named headers, query parameters and
body fields on top of that — the connector's own configured auth is
what actually authenticates the tool's calls, not anything recorded
in the capture.
Closes HelpCode-ai#1005
helpcodeai
left a comment
There was a problem hiding this comment.
Thanks @rangarajan19, this is a solid start: parser, tests and docs are all there and CI is green. Before merging, the import has to make sure no captured credential survives. #1005 says "never keeps a credential value", and a HAR file is full of them. I ran the parser locally on a crafted HAR, and these cases got through:
Must fix
-
Nested body secrets are kept as defaults (
har.parser.ts~215–217). A top-level body key that isn't credential-named keeps its whole value as the default.{"user":{"password":"hunter2…"}}became auserparameter whose default contains the password, and every org member and the AI client can see it. Either keep only scalar defaults, or walk the body recursively and drop credential-looking keys and values. -
Credentials whose names miss the regex survive:
?key=AIza…Ocp-Apim-Subscription-KeyX-Access-KeyX-Firebase-AppCheck(a JWT)X-Custom: Bearer …
Please reuse the shared helpers in
packages/backend/src/common/secret-literals.util.ts(isCredentialHeaderName,isSecretQueryName,findSecretLiterals/isCredentialLiteral) on top of the issue's regex, and check values too (Bearer, JWTs, known key prefixes). Since #1054, imports also go throughconnectors/secret-extraction.util.ts. A HAR import should drop captured values rather than turn them into connector env vars: they belong to the user's browser session, not to the connector. -
Header allowlist. Every other header is frozen as a static tool header today, including
If-None-Match/If-Modified-Since(the tool would get 304s),traceparent,baggage,cache-controlandpriority. Prefer an allowlist (Accept,Content-Type, plus API-specificX-…headers that pass the secret checks), or at least drop the conditional and tracing headers. -
Browser upload (
frontend/.../connectors/[id]/page.tsx~69–90). Send only the entries on the connector's origin. Strip credential-named query and body fields before upload, and dropresponseentirely: the backend doesn't need it.
Smaller
- Path prefix boundary (~305): with base
/v1, a request to/v1beta/xis imported asbeta/x. Match on a/boundary. - Object/array defaults get
type: 'string'frominferType. Give themobject/array, or skip them.
Tests to add
- a nested secret in the body;
?key=;Ocp-Apim-Subscription-Key;- a
Bearervalue in a custom header.
Each should assert that no captured value ends up in tool defaults or headers, and that no env var is created from the HAR.
Happy to re-review as soon as you push. 🎃
Recurse into nested JSON bodies so a nested secret (e.g. user.password) is dropped instead of surviving inside its parent object's default. Reuse the shared secret-literals helpers on top of the issue's naming regex and check values too, so credentials the regex alone misses (Ocp-Apim-Subscription-Key, X-Access-Key, a bare ?key=, a Bearer value or JWT in an arbitrarily-named header) are dropped. Switch headers from a denylist to an allowlist (Accept + X-*) so conditional/tracing headers are excluded by default. Fix the base-URL prefix check to match on a / boundary, and stop objects/arrays from being typed as string defaults. On the frontend, restrict uploaded entries to the connector's own origin, strip credential-named query/body fields before upload, and drop response data entirely. Adds a controller-level test proving the full import pipeline never promotes a HAR-captured credential into a connector env var, plus parser tests for each case called out in review.
|
Thanks for the detailed review @helpcodeai — pushed fixes for all of it. Must fix
Smaller
Tests
One thing I didn't change, and want your call on: a credential baked directly into a URL path segment (Telegram-style, I left it alone because it reads to me as the connector's own routing secret (same value for every caller, not a per-request session artifact), so promoting it seemed consistent with how the other import sources already treat that exact pattern. But your "a HAR import should drop captured values rather than turn them into connector env vars" didn't carve out an exception for this, so I don't want to guess — should this case also be dropped instead of promoted, or is treating it like the other import sources fine here? Happy to push a follow-up once you weigh in. |
A form body was mapped field by field but the tool had no bodyEncoding, and Content-Type is not a replayed header, so the engine sent the fields as JSON. Set bodyEncoding: form-urlencoded (reading postData.text when an exporter leaves params out) and cover a path token moving into a connector variable through the import guard.
|
Thanks @rangarajan19, I went through the new commits and everything from my review is in: nested bodies are walked and the secret is dropped at any depth, the shared secret helpers are used, headers are an allowlist now, and the browser only uploads entries on the connector's origin with no On your question about a token in a path segment ( One small thing I found while reading, and since edits by maintainers are on I just fixed it on your branch (3f06105): form bodies ( |
helpcodeai
left a comment
There was a problem hiding this comment.
Form encoding fixed in 3f06105, everything else from the review is in. Approving.
Closes #1005.
Summary
HarParser(packages/backend/src/connectors/parsers/har.parser.ts): validates HAR 1.2, keeps only entries under the connector's base URL, skipsOPTIONS/HEAD, static assets and WebSocket entries, collapses same-template requests (/orders/123+/orders/456→ one tool with{id}), and maps query strings and JSON/form bodies to tool parameters.Cookie,Authorization,Proxy-Authorization, HTTP/2 pseudo-headers and any header/query/body field named like a credential (token,secret,password,api_key,session,csrf,xsrf,auth,signature,sig). A credential-named body field (e.g. a login form'spassword) still becomes a parameter, just with no captured default.'har'added as anImportToolsDto.source, wired through the existing additive-import path (likecurl/json); the response now reports how many entries were skipped and why (other-origin, static/socket, duplicate).response.content,request/responsecookies, and credential-looking headers (includingSet-Cookie) are stripped — with a size check so an oversized capture after slimming is rejected in the panel instead of failing the request with a 413 (per review feedback on the issue).docs/connectors/rest.md, example added todocs/api-reference.md.Why
Many internal tools and SaaS products have an API but no OpenAPI spec or Postman collection — the only description of it is what their own web app sends. A HAR file (DevTools → Network → "Save all as HAR") records exactly those requests.
Test plan
cd packages/backend && npx jest src/connectors/parsers/har.parser.spec.ts— 8/8 passing (origin/static-asset filtering, path-id collapsing, query/body mapping, base-URL-prefix handling, credential-scrubbing assertion over the full serialized output, determinism, invalid-file rejection).cd packages/backend && npx jest src/connectors— 1343/1343 passing (no regressions from the new constructor parameter onConnectorsController).npx tsc --noEmitclean on bothpackages/backendandpackages/frontend.npm run lintclean on both packages (no new warnings).jsonplaceholder.typicode.com(GET/posts/1, GET/posts/2, GET/comments?postId=1, POST/posts), created a REST connector against that base URL on a local instance, imported the HAR through the new UI — got "created 3, updated 0 (skipped 0 other-origin, 0 static/socket, 1 duplicate entries)" — and ran the generatedget_poststool withid: 1, which returned the real API response.Out of scope (per the issue)
Response-schema/
outputSchemainference, auto-detecting or configuring the connector's auth scheme, creating a new connector from a HAR in one step, and splitting GraphQL requests in a HAR into separate tools.