Skip to content

Commit 625bb08

Browse files
authored
fix(vscode): resolve design-time to runtime object names in artifact inspectors (#192)
* docs: design spec for design-time to runtime name resolution in artifact inspectors * docs: implementation plan for runtime name resolution * feat(vscode): add resolveRuntimeName for design-time to runtime object names * fix(vscode): inject runtime object name into artifact inspectors * chore(vscode): bump extension to 0.1.9 for runtime name resolution fix * fix(vscode): sanitize resolved runtime names to prevent webview script injection - resolveRuntimeName now validates parsed names against a safe HANA identifier grammar ([A-Za-z0-9_.:]); unsafe DDL/namespace values fall through to the sanitized filename rule instead of passing verbatim. - htmlProvider embeds the webview route via JSON.stringify so the value is escaped at the sink regardless of caller. - Adds 4 security tests (quote/angle-bracket DDL, quoted filename, malicious .hdinamespace prefix). Addresses automated security review (xss-injection). * chore(vscode): bump extension to 0.1.10 for injection sanitization fix --------- Co-authored-by: Thomas Jung <12159356+jung-thomas@users.noreply.github.com>
1 parent 74f83c3 commit 625bb08

8 files changed

Lines changed: 1130 additions & 11 deletions

File tree

docs/superpowers/plans/2026-07-15-runtime-name-resolution.md

Lines changed: 508 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
# Design: Design-time → Runtime Name Resolution for Artifact Inspectors
2+
3+
**Date:** 2026-07-15
4+
**Component:** VSCode extension (`vscode-extension/`)
5+
**Status:** Approved
6+
7+
## Problem
8+
9+
When a user opens an HDI design-time file in a hana-cli custom editor (e.g.
10+
`cds.outbox.Messages.hdbtable`), the artifact inspector injects the file's
11+
**design-time** name into the inspect input field. HANA does not know that
12+
name, so the lookup fails with "Invalid Input Table."
13+
14+
The design-time name and the runtime object name differ:
15+
16+
| Design-time (filename) | Runtime (deployed object) |
17+
| ------------------------------ | ------------------------- |
18+
| `cds.outbox.Messages.hdbtable` | `cds_outbox_Messages` |
19+
| `star.wars.X.hdbview` | `star_wars_X` |
20+
21+
Today, [`artifactInspector.ts`](../../../vscode-extension/src/editors/artifactInspector.ts)
22+
(lines 78–80) strips only the file extension:
23+
24+
```ts
25+
const filename = path.basename(document.uri.fsPath)
26+
const dotIndex = filename.lastIndexOf('.')
27+
const name = dotIndex > 0 ? filename.substring(0, dotIndex) : filename
28+
```
29+
30+
For `cds.outbox.Messages.hdbtable` this yields `cds.outbox.Messages`, which is
31+
then injected verbatim via a route query param
32+
(`/inspect-table?table=cds.outbox.Messages`).
33+
34+
### Why the lookup fails
35+
36+
1. **Dot → underscore transformation.** CAP/HDI replaces `.` with `_` in the
37+
deployed object name. The file contains the real name in its DDL, e.g.
38+
`COLUMN TABLE cds_outbox_Messages (...)`.
39+
2. **Case sensitivity.** The backend matches names **exactly and
40+
case-sensitively** — see
41+
[`dbInspect.js`](../../../utils/dbInspect.js) `getTable`, which queries
42+
`SYS.TABLES` with `AND TABLE_NAME = ?` (not `LIKE`, not uppercased). HDI
43+
deploys objects with quoted, mixed-case identifiers
44+
(`cds_outbox_Messages`), so any casing change breaks the match.
45+
46+
## Goal
47+
48+
For every connected artifact inspector editor, inject the correct **runtime**
49+
object name — preserving exact case — so the inspect lookup succeeds without
50+
the user editing the field manually.
51+
52+
## Scope
53+
54+
**In scope** — the artifact kinds registered in `ARTIFACT_CONFIGS`
55+
([`artifactInspector.ts`](../../../vscode-extension/src/editors/artifactInspector.ts)
56+
lines 14–22):
57+
58+
| Kind | File pattern(s) | Route |
59+
| --------- | ---------------------------------- | ------------------ |
60+
| table | `.hdbtable`, `.hdbmigrationtable` | `/inspect-table` |
61+
| view | `.hdbview` | `/inspect-view` |
62+
| procedure | `.hdbprocedure` | `/call-procedure` |
63+
| function | `.hdbfunction` | `/inspect-function`|
64+
| synonym | `.hdbsynonym` | `/inspect-table` |
65+
| role | `.hdbrole` | `/inspect-table` |
66+
| sequence | `.hdbsequence` | `/inspect-table` |
67+
68+
**Out of scope**
69+
70+
- `calcViewEditor.ts` — the `.hdbcalculationview` editor is a separate provider
71+
with its own XML handling; not touched by this change.
72+
- Server (`routes/`), CLI, and Vue view changes — the resolution is entirely
73+
extension-side.
74+
75+
## Approach
76+
77+
A new extension-side resolver module reads the design-time file and extracts
78+
the runtime name from its DDL/content. This is authoritative: it uses the exact
79+
name the deployer wrote, preserving case and any namespace prefix. If parsing
80+
fails, it falls back to a filename-based naming rule.
81+
82+
**Decisions confirmed during brainstorming:**
83+
84+
- **Translation source:** parse file content (not a naming rule alone, not a
85+
live DB lookup).
86+
- **Fallback:** naming-rule fallback (strip extension, dots→underscores, apply
87+
`.hdinamespace` prefix) when parsing fails — always inject a best-effort name.
88+
- **Namespace:** handled by the parse path automatically (DDL is already fully
89+
qualified); `.hdinamespace` is only read for the fallback rule.
90+
- **Location:** extension-side resolver module (TypeScript), unit-testable in
91+
the extension test suite; no server round-trip.
92+
93+
## Component: `resolveRuntimeName`
94+
95+
New file: `vscode-extension/src/editors/runtimeName.ts`
96+
97+
```ts
98+
/**
99+
* Resolve the runtime (deployed) object name for an HDI design-time file.
100+
* Reads the file content and extracts the name from its DDL/JSON; falls back
101+
* to a filename-based naming rule if parsing fails. Never throws.
102+
*
103+
* @param fsPath absolute path to the design-time file
104+
* @param kind artifact kind from ArtifactConfig ('table' | 'view' | ...)
105+
* @returns the runtime object name, case preserved
106+
*/
107+
export function resolveRuntimeName(fsPath: string, kind: string): string
108+
```
109+
110+
### Per-kind parsers
111+
112+
Matched by the `kind` value already present on each `ArtifactConfig`. All
113+
parsers operate on the file's text content (read synchronously; these files are
114+
small).
115+
116+
| Kind | Content type | Extraction rule |
117+
| --------- | ------------ | ------------------------------------------------------ |
118+
| table | DDL | first match of `(?:COLUMN\|ROW)?\s*TABLE\s+<ident>` |
119+
| view | DDL | `VIEW\s+<ident>\s+AS` |
120+
| function | DDL | `FUNCTION\s+<ident>` |
121+
| procedure | DDL | `PROCEDURE\s+<ident>` |
122+
| sequence | DDL or JSON | detect: JSON → top-level name key; else `SEQUENCE\s+<ident>` |
123+
| synonym | JSON | top-level object key (the synonym's own name) |
124+
| role | JSON | top-level object key (the role's own name) |
125+
126+
Where `<ident>` is either a bare identifier (`cds_outbox_Messages`) or a
127+
double-quoted identifier (`"cds_outbox_Messages"`), optionally schema-qualified
128+
(`schema.name`, `"schema"."name"`).
129+
130+
### Identifier normalization
131+
132+
- Strip surrounding double-quotes but **preserve inner case**.
133+
- For **DDL** names that are schema-qualified with a dot (`schema.name`,
134+
`"schema"."name"`), take the **last dot segment** (the object name); the
135+
schema is supplied separately by the inspector (defaults to
136+
`**CURRENT_SCHEMA**`).
137+
- For **JSON** keys (synonym/role/sequence), use the key verbatim after
138+
quote-stripping. A `::` namespace separator is part of the runtime object
139+
name and is **kept** (not split), unlike a dot schema qualifier.
140+
- Do **not** upper/lowercase the resultinject verbatim so it matches the
141+
quoted mixed-case identifier HDI deployed.
142+
143+
### Fallback rule
144+
145+
Triggered when: file read fails, content does not match the parser, the kind is
146+
unknown, or the extracted name is empty.
147+
148+
1. Strip the file extension from the basename.
149+
2. Replace `.` with `_`.
150+
3. If an ancestor `.hdinamespace` file exists with a non-empty `name` field,
151+
prepend `<name>::`.
152+
153+
This mirrors HDI's own naming transformation and guarantees the field is always
154+
populated with a reasonable guess (never worse than today's behavior).
155+
156+
## Integration
157+
158+
In [`artifactInspector.ts`](../../../vscode-extension/src/editors/artifactInspector.ts),
159+
`resolveCustomEditor` replaces the extension-strip logic (lines 7780) with:
160+
161+
```ts
162+
const name = resolveRuntimeName(document.uri.fsPath, this._config.kind)
163+
```
164+
165+
The remainder of the flowbuilding `routeWithName`, the webview content, the
166+
message handlersis unchanged. The resolved name flows through the existing
167+
`queryKey` query param mechanism to the target Vue view.
168+
169+
## Error handling
170+
171+
- `resolveRuntimeName` never throws. All failure modes (I/O error, malformed
172+
content, unsupported kind) degrade to the fallback rule.
173+
- No new user-facing error surface; the inspector continues to render, and a
174+
wrong guess is correctable by the user in the field as today.
175+
176+
## Testing
177+
178+
Unit tests in `vscode-extension/test/suite/` (e.g. `runtimeName.test.ts`),
179+
using real DDL/JSON fixtures mirroring the samples observed in the
180+
`cloud-cap-hana-swapi` project:
181+
182+
**Parse-path cases (one per kind):**
183+
184+
- table: `COLUMN TABLE cds_outbox_Messages (...)``cds_outbox_Messages`
185+
- view: `VIEW star_wars_CloneWarsChronologicalOrder AS SELECT ...`
186+
`star_wars_CloneWarsChronologicalOrder`
187+
- function / procedure / sequence: analogous DDL snippets
188+
- synonym / role: JSON with a single top-level name key
189+
190+
**Normalization cases:**
191+
192+
- quoted identifier: `TABLE "cds_outbox_Messages"` → unquoted, case preserved
193+
- schema-qualified: `"MYSCHEMA"."cds_outbox_Messages"` → last segment
194+
- mixed case preserved (no upper/lowercasing)
195+
196+
**Fallback cases:**
197+
198+
- empty file → naming rule
199+
- unknown kind → naming rule
200+
- unparseable content → naming rule
201+
- `.hdinamespace` with non-empty `name` → `<name>::` prefix applied
202+
- `.hdinamespace` empty / absent → no prefix
203+
204+
## Files changed
205+
206+
| File | Change |
207+
| ------------------------------------------------- | ------------------------------- |
208+
| `vscode-extension/src/editors/runtimeName.ts` | new — resolver + parsers |
209+
| `vscode-extension/src/editors/artifactInspector.ts` | use `resolveRuntimeName` |
210+
| `vscode-extension/test/suite/runtimeName.test.ts` | new — unit tests |
211+
212+
Per project memory: bump the extension version before repackaging the `.vsix`,
213+
and package via `npm run package` (not `vsce package --no-dependencies`).

vscode-extension/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vscode-extension/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "hana-cli",
33
"displayName": "hana-cli Tools for SAP HANA",
44
"description": "Visual editors and database tools for SAP HANA powered by hana-cli",
5-
"version": "0.1.8",
5+
"version": "0.1.10",
66
"publisher": "SAP-samples",
77
"icon": "icon.png",
88
"license": "Apache-2.0",

vscode-extension/src/editors/artifactInspector.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as vscode from 'vscode'
2-
import * as path from 'path'
32
import { getWebviewContent, getWebviewOptions } from '../webview/htmlProvider.js'
43
import { ensureServer, trackWebviewOpen, trackWebviewClose } from '../extension.js'
4+
import { resolveRuntimeName } from './runtimeName.js'
55

66
interface ArtifactConfig {
77
viewType: string
@@ -74,10 +74,11 @@ class ArtifactInspectorProvider implements vscode.CustomReadonlyEditorProvider {
7474
enableScripts: true,
7575
}
7676

77-
// Parse artifact name from the filename (strip extension)
78-
const filename = path.basename(document.uri.fsPath)
79-
const dotIndex = filename.lastIndexOf('.')
80-
const name = dotIndex > 0 ? filename.substring(0, dotIndex) : filename
77+
// Resolve the runtime (deployed) object name from the design-time file.
78+
// The filename is the design-time name (e.g. cds.outbox.Messages.hdbtable);
79+
// HANA knows the runtime name (e.g. cds_outbox_Messages), matched
80+
// case-sensitively. resolveRuntimeName reads the file content to get it.
81+
const name = resolveRuntimeName(document.uri.fsPath, this._config.kind)
8182

8283
const port = await ensureServer(this._context)
8384

0 commit comments

Comments
 (0)