Skip to content

Masked credentials are not substituted in the URL query, so an API that takes its key only there can't be used #752

Open

Description

@mmmhoj

Summary

A mode: "mask" credential is substituted in headers and in the body of a request to one of its injectHosts, but not in the request target. Some APIs accept the key only as a query parameter. OpenDART (Korea's official corporate disclosure API) is the one I hit: every call needs ?crtfc_key=<40-char key>, and it rejects the key in a header or in a POST form body. With the key masked, the sentinel goes out as is and the call fails. There is no setting that fixes it today; filterRequest can set headers since #663 but cannot touch the target.

Reproduction

0.0.81 from npm, macOS, Node 22. settings.json:

{
  "network": { "allowedDomains": ["opendart.fss.or.kr"], "deniedDomains": [], "tlsTerminate": {} },
  "filesystem": { "denyRead": [], "allowRead": [], "allowWrite": [], "denyWrite": [] },
  "credentials": {
    "envVars": [{ "name": "DART_API_KEY", "mode": "mask", "injectHosts": ["opendart.fss.or.kr"] }]
  }
}
srt --settings settings.json -- sh -c \
  'curl -s "https://opendart.fss.or.kr/api/company.json?crtfc_key=$DART_API_KEY&corp_code=00126380"'

Inside the sandbox $DART_API_KEY is fake_value_… and OpenDART answers {"status":"100","message":"잘못된 인증키입니다…"} (invalid key). The same curl on the host with the real key answers "status":"000".

Proposal

Add a query target to injectInto from #715: "query:<name>".

{ "name": "DART_API_KEY", "mode": "mask", "injectHosts": ["opendart.fss.or.kr"],
  "injectInto": ["query:crtfc_key"] }
  • Substitute only when that parameter's value is exactly the sentinel. Every other byte of the target goes out as spelled, so this stays inside what Never rewrite a request target silently #698 settled.
  • Never add the parameter. We tried "append the key to every request toward the host" first, and OpenDART's guide page /guide/main.do echoes its own URL into the HTML, so a request made without the key came back with the real key in the response body. Swapping only where the client put the sentinel avoids that, the same way headers work.
  • Same scope rules as today: injectHosts only, TLS-terminated only (masking already requires tlsTerminate).
  • filterRequest, violation lines and logs keep seeing the target the client sent, i.e. with the sentinel (fix(proxy): honor ignoreViolations and redact query strings on proxy deny lines #441 already trims queries from deny lines).
  • Opt-in: not part of the default scope, so an existing config sees no change. A presigned SigV4 request keeps its current handling (Re-sign AWS SigV4 requests at the proxy for masked credentials #400).

What we do in the meantime

A two-line patch to forwardUpstream (0.0.78) that forwards the path our header hook returns, and the swap above in our own proxy code. It has to be redone for 0.0.79+, which is why I'd rather see it upstream. I can send the PR on top of #715 if this shape works for you.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions