@saptools/cf-hana 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,70 @@
2
2
 
3
3
  <!-- cspell:words VARCHAR -->
4
4
 
5
+ ## 0.5.1 - 2026-07-25
6
+
7
+ - **Security fix:** a `WITH`-prefixed write (e.g. `WITH x AS (...) DELETE
8
+ FROM ...`) and a bare `CALL <procedure>()` both previously slipped past
9
+ the `--read-only` and destructive-statement guards entirely — the former
10
+ was always classified as a harmless `SELECT` regardless of what it
11
+ actually did, and the latter was never classified as destructive at all,
12
+ so it ran with no `--allow-destructive` confirmation even in the default
13
+ (non-read-only) mode. Both are now correctly classified and guarded.
14
+ Anyone relying on `--read-only` or the destructive-statement guard as a
15
+ hard trust boundary should treat this as the release that closes that gap.
16
+ The automatic pre-write backup now also correctly recognizes and protects
17
+ a `WITH`-prefixed `UPDATE`/`DELETE`/`UPSERT`/`MERGE`, matching what a
18
+ non-`WITH` equivalent write already produced.
19
+ - Disable SAP HANA Cloud's reactive mid-auth redirect on every connection
20
+ (direct and tunneled): on a genuine multi-node HANA Cloud instance, the
21
+ server can redirect an already-established connection to an internal
22
+ per-node hostname that isn't reachable outside SAP's own network, which
23
+ previously defeated the SSH-tunnel fallback by silently abandoning the
24
+ tunnel for a fresh, untunneled connection that failed the same way the
25
+ original direct connection did.
26
+ - Fix the SSH-tunnel fallback's candidate discovery calling `cf apps`
27
+ (measured at up to ~20s against a large space) before ever trying the
28
+ already-known target app or a cached "last worked" app, which could
29
+ starve the entire shared fallback budget before a working candidate got a
30
+ chance. Known candidates are now always tried first; `cf apps` is only
31
+ called if none of them work, and is bounded to whatever budget remains
32
+ rather than retried on its own separate timeout/retry policy.
33
+ - Cap the direct-connect attempt's own timeout to the tunnel fallback's
34
+ per-candidate ceiling whenever a fallback is available, so a silently
35
+ hanging (rather than actively refused) direct connection can no longer
36
+ consume its full configured timeout before the tunnel path gets a chance
37
+ — lowering the documented worst-case latency from ~85s to ~40s.
38
+ - Fix `--refresh-tunnel` leaking the SSH process it superseded; the old
39
+ tunnel is now terminated instead of running until its keepalive elapses.
40
+ - Surface a failed tunnel candidate's actual stderr (e.g. "SSH disabled for
41
+ this app") via the existing connectivity status output on stderr, instead
42
+ of discarding it silently.
43
+ - Fix a freshly-established or reused tunnel being cached as usable even
44
+ when its own post-connect setup failed in a way that looked like a
45
+ dropped connection rather than a genuine, actionable HANA rejection.
46
+ - Bound how long a process waits for another concurrent invocation to
47
+ finish establishing the same host's tunnel, so the entire shared deadline
48
+ can no longer be spent waiting on a leader whose owning process has
49
+ already died.
50
+
51
+ ## 0.5.0 - 2026-07-25
52
+
53
+ - Add an SSH-tunnel fallback for HANA Cloud hosts unreachable directly (e.g.
54
+ IP-allowlisted landscapes): on a classified connectivity failure, discover
55
+ a jump-host app in the same org/space via `cf apps`, open a local
56
+ `cf ssh -L` port-forward, and retry through it. Zero behavior change when
57
+ the direct connection succeeds.
58
+ - Add `--tunnel` to skip the direct attempt and connect via a tunnel
59
+ immediately, and `--refresh-tunnel` to bypass a cached/live tunnel and
60
+ force a fresh attempt. No disable switch: the fallback can only help or
61
+ no-op, and rethrows the original connection error unchanged on total
62
+ failure.
63
+ - Persist and reuse the live tunnel under `~/.saptools/cf-hana/tunnel/`
64
+ across every connection this CLI's pool opens and across separate
65
+ invocations run in a row against the same host, with a race-free
66
+ concurrent-establishment marker and automatic reaping of dead or
67
+ cross-org tunnels.
68
+
5
69
  ## 0.4.0 - 2026-07-14
6
70
 
7
71
  - Back up `REPLACE` and matched `MERGE INTO` pre-images, cap backup size, and
package/README.md CHANGED
@@ -105,7 +105,8 @@ cf-hana result <command> Inspect saved query refs
105
105
 
106
106
  Common options: `--refresh` (deprecated compatibility flag; binding discovery is already live), `--role <runtime|hdi>`, `--binding <name>` /
107
107
  `--binding-index <n>`, `--timeout <ms>`, `--read-only`, `--allow-destructive`,
108
- `--limit <n>`, `--no-auto-limit`. The `query` command also accepts
108
+ `--limit <n>`, `--no-auto-limit`, `--tunnel`, `--refresh-tunnel` (see
109
+ [Connectivity fallback](#connectivity-fallback)). The `query` command also accepts
109
110
  `--param <value>` (repeatable), `--cell-limit <n>`, `--save`, `--no-auto-save`,
110
111
  `--format <table|json|json-compact|csv>`, `--result-ttl-minutes <n>`, and
111
112
  `--refresh-metadata`. `tables` and `columns` support the same four format values.
@@ -351,6 +352,95 @@ mode `0600`.
351
352
  The guard is a convenience, not a security control: always pass values as bound
352
353
  parameters.
353
354
 
355
+ ## Connectivity fallback
356
+
357
+ HANA Cloud instances are frequently IP-allowlisted to only accept connections
358
+ from inside the same Cloud Foundry landscape. When a direct connection fails
359
+ in a way that means the initial socket could never be established — not an
360
+ authentication, privilege, or query failure — `cf-hana` can retry through an
361
+ SSH port-forward opened via `cf ssh` against another app in the same org/space,
362
+ which usually can reach the host even when your machine cannot.
363
+
364
+ - **`auto` (default)**: tries the direct connection first, with zero added
365
+ behavior when it succeeds. Only a classified connectivity failure triggers
366
+ the fallback: discover a jump-host app (the target app itself, then a few
367
+ other started apps via `cf apps`), open a local port-forward, and retry
368
+ through `127.0.0.1`.
369
+ - **`--tunnel`**: skips the direct attempt and connects via a tunnel
370
+ immediately — for a host already known to be unreachable directly, so you
371
+ are not paying the connect-timeout cost (up to 60s) on every invocation.
372
+ - **`--refresh-tunnel`**: bypasses a cached/live tunnel and forces a fresh
373
+ establishment attempt.
374
+
375
+ There is deliberately no flag to disable this capability: a tunnel attempt
376
+ only ever engages after a direct failure (or when explicitly requested via
377
+ `--tunnel`), and on total failure it rethrows the original connection error
378
+ unchanged, so it can only help or no-op.
379
+
380
+ The live tunnel is reused — both across every connection this CLI's own pool
381
+ opens in one invocation, and across separate `cf-hana` invocations run in a
382
+ row against the same host (this CLI's realistic dominant usage pattern, e.g.
383
+ an AI agent running several queries back to back) — instead of re-negotiating
384
+ SSH on every single command. State lives at `~/.saptools/cf-hana/tunnel/`
385
+ (mode `0700`/`0600`, no credentials). A cached tunnel is closed immediately,
386
+ regardless of its remaining lifetime, the moment a later invocation targets a
387
+ different Cloud Foundry org — a different client's landscape never keeps an
388
+ unattended live SSH path open just because its keepalive has not lapsed.
389
+
390
+ In the worst case (a silently-dropping network path, so the direct attempt
391
+ runs its full connect timeout, and no candidate app works either), `auto`
392
+ mode takes roughly 15s (direct, capped once a tunnel fallback is available —
393
+ see below) + 25s (tunnel budget) ≈ 40s before surfacing a final error —
394
+ bounded and predictable, not open-ended. Use `--tunnel` to skip the direct
395
+ cost entirely for a host you already know is unreachable.
396
+
397
+ Whenever a tunnel fallback is available at all (`auto` mode, the default),
398
+ the direct attempt's own timeout is capped at the tunnel side's own
399
+ per-candidate ceiling (15s by default) even if a longer timeout is
400
+ configured — a silently-hanging (rather than actively refused) direct
401
+ connection would otherwise consume its entire configured timeout before the
402
+ tunnel path got a chance at all. This only ever shortens an
403
+ otherwise-hanging attempt; it never affects a host that refuses quickly, or
404
+ a configured timeout that is already shorter.
405
+
406
+ `cf-hana` also disables SAP HANA Cloud's reactive mid-auth redirect (used
407
+ for per-node/pod locality routing) on every connection, direct or tunneled:
408
+ without this, a multi-node HANA Cloud instance can redirect an
409
+ already-established connection to a different internal hostname that isn't
410
+ reachable outside SAP's own network, silently abandoning a working SSH
411
+ tunnel for a fresh, untunneled connection that fails for the same reason the
412
+ original direct connection did. The original bound host is always a real,
413
+ working endpoint for your binding, so this has no effect on which schema,
414
+ user, or data you reach.
415
+
416
+ ### Known limitations
417
+
418
+ - **Cross-org reaper and concurrent processes.** Every connection attempt
419
+ closes any cached tunnel tagged for a different Cloud Foundry org than the
420
+ current target, regardless of remaining lifetime — intentional hygiene so
421
+ switching orgs never leaves a stale tunnel open. On a shared machine, this
422
+ can also close another, unrelated, concurrently-running invocation's
423
+ active tunnel to a different org; there is no portable, low-cost way to
424
+ detect "is this port actively in use by someone else" from outside that
425
+ TCP session.
426
+ - **Stale-tunnel reaping trusts the recorded pid.** The reaper terminates a
427
+ dead or expired tunnel's process by its recorded pid, with no additional
428
+ identity check (e.g. confirming the pid still refers to a `cf ssh`
429
+ process). On a long-uptime machine, pid reuse could in principle target an
430
+ unrelated process; this is a narrow race not worth a platform-specific
431
+ check for.
432
+ - **Programmatic (non-CLI) pool usage can strand idle connections.** The
433
+ connection pool reuses the most-recently-released idle connection first
434
+ (LIFO); a long-lived process that opens a burst of connections and then
435
+ settles into serial one-at-a-time use can leave earlier connections
436
+ idle-but-unreaped for the pool's lifetime. The CLI itself always drains
437
+ its pool at the end of every command, so this only affects long-lived
438
+ programmatic use of the library.
439
+ - **Pre-write backups are never pruned.** Unlike SQL history (5-day
440
+ retention) and saved query results (TTL plus `result prune`/`result
441
+ clear`), files under `~/.saptools/cf-hana/backups/` accumulate
442
+ indefinitely with no retention policy or cleanup command.
443
+
354
444
  ## Requirements
355
445
 
356
446
  - Node.js >= 20.