Skip to content

feat(extensions): add databricks skill pack scaffold#56592

Closed
Kansodata wants to merge 9 commits into
openclaw:mainfrom
Kansodata:feat/kansodata-databricks-skill-pack
Closed

feat(extensions): add databricks skill pack scaffold#56592
Kansodata wants to merge 9 commits into
openclaw:mainfrom
Kansodata:feat/kansodata-databricks-skill-pack

Conversation

@Kansodata

Copy link
Copy Markdown
Contributor

Summary

  • add a new plugin scaffold @kansodata/databricks under extensions/databricks
  • register a packaged skill at skills/databricks/SKILL.md
  • expose plugin metadata through openclaw.plugin.json and extension entrypoint
  • add labeler mapping for the new extension path

Why

  • this enables installation using a scoped package (@kansodata/databricks)
  • keeps the contribution as a first-party, self-initiated plugin-based skill pack

Validation

  • pre-commit checks ran during commit
  • repo is clean after scaffold and branch split

@greptile-apps

greptile-apps Bot commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a new @kansodata/databricks skill-pack scaffold under extensions/databricks, following the same structural pattern as extensions/open-prose. The extension registers a skills-only plugin entry, ships a SKILL.md for Databricks SQL / jobs / Unity Catalog workflows, and adds the corresponding labeler mapping.

Key findings:

  • P1 — Package scope violates the @openclaw/<id> naming convention (extensions/databricks/package.json:2): CLAUDE.md / AGENTS.md require all repo-owned workspace plugins to use @openclaw/<id> (or an approved suffix form). Every other extension in the repo conforms. The use of a third-party @kansodata/ scope requires either renaming to @openclaw/databricks or an explicitly documented, test-covered exception — neither of which is present here.
  • P2 — Version string inconsistency (extensions/databricks/package.json:3): "0.0.1" does not match the date-based versioning format ("2026.3.27") used across all other extensions.
  • P2 — Labeler entry out of alphabetical order (.github/labeler.yml): The "extensions: databricks" block is placed after "extensions: deepseek" but should appear before it (databricks sorts before deepseek).

Confidence Score: 4/5

Not safe to merge as-is — the package scope violates the documented @openclaw/<id> naming requirement and may fail the check-additional CI architecture gate.

One P1 finding: the @kansodata/databricks package name deviates from the explicitly documented repo-wide invariant that all workspace plugins use @openclaw/<id>. The rest of the scaffold closely mirrors the existing open-prose extension and looks correct. Two P2 items (version format and labeler ordering) are minor and don't block merge on their own.

extensions/databricks/package.json — package scope and version need to be corrected before landing.

Important Files Changed

Filename Overview
extensions/databricks/package.json Package scope uses @kansodata/databricks instead of the required @openclaw/databricks; version 0.0.1 deviates from the repo-wide date-based convention.
.github/labeler.yml Adds the extensions: databricks labeler rule; correct glob pattern, but the entry is placed after extensions: deepseek when it should appear before it alphabetically.
extensions/databricks/index.ts Scaffold entry point following the same shape as extensions/open-prose/index.ts; no functional code, only metadata registration.
extensions/databricks/openclaw.plugin.json Plugin manifest correctly mirrors the open-prose pattern; id, name, description, skills, and configSchema all look correct.
extensions/databricks/runtime-api.ts Re-export barrel for the plugin SDK; identical pattern to other extensions, no issues.
extensions/databricks/skills/databricks/SKILL.md Well-structured skill file with clear scope, input prompts, output format, and three templated workflows (SQL, jobs, governance). No issues.
extensions/databricks/README.md Install and enable instructions look correct; uses the @kansodata/databricks install spec which would need updating if the package is renamed.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/databricks/package.json
Line: 2

Comment:
**Package scope violates the repo naming convention**

`CLAUDE.md` / `AGENTS.md` explicitly requires that all repo-owned workspace plugins use the `@openclaw/<id>` scope (or an approved suffix form like `-provider`, `-plugin`, etc.). Every other extension in this repo follows that convention (e.g. `@openclaw/open-prose`, `@openclaw/deepseek-provider`). The `@kansodata/` scope here is a third-party vendor scope.

The guideline also states:

> "Exceptions must be explicit and covered by the repo invariant test."

This PR includes neither an updated invariant test nor an explicit in-repo exception for `@kansodata/databricks`. If the `check-additional` CI architecture gate enforces the naming invariant, this PR will fail.

Either rename the package to `@openclaw/databricks` (the expected form for first-party workspace plugins) or add a documented, test-covered exception before landing.

```suggestion
  "name": "@openclaw/databricks",
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/databricks/package.json
Line: 3

Comment:
**Version does not follow the repo date-based convention**

All existing extension packages use a date-based version string (e.g. `"2026.3.27"`). `0.0.1` is inconsistent with this pattern and may cause confusion or break any tooling that expects the canonical versioning format.

```suggestion
  "version": "2026.3.28",
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: .github/labeler.yml
Line: 244-248

Comment:
**Labeler entry placed out of alphabetical order**

The new `"extensions: databricks"` entry is inserted after `"extensions: deepseek"`, but `databricks` (d-a-t-a…) sorts before `deepseek` (d-e-e-p…) alphabetically. The surrounding entries follow alphabetical ordering. Move this block before the `"extensions: deepseek"` entry to stay consistent.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(extensions): add databricks skill p..." | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f100aec792

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/databricks/package.json Outdated
@@ -0,0 +1,12 @@
{
"name": "@kansodata/databricks",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rename bundled package to allowed @openclaw form

This package name violates the bundled plugin naming invariant: src/plugins/bundled-plugin-naming.test.ts only allows @openclaw/<id> (plus approved suffixes), so setting @kansodata/databricks will make that guardrail fail when the test suite runs and breaks CI for this commit. If a non-@openclaw namespace is intentional, the invariant/docs need to be updated in the same change; otherwise this should be renamed to an allowed canonical form.

Useful? React with 👍 / 👎.

Comment thread extensions/databricks/package.json Outdated
@@ -0,0 +1,12 @@
{
"name": "@kansodata/databricks",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Package scope violates the repo naming convention

CLAUDE.md / AGENTS.md explicitly requires that all repo-owned workspace plugins use the @openclaw/<id> scope (or an approved suffix form like -provider, -plugin, etc.). Every other extension in this repo follows that convention (e.g. @openclaw/open-prose, @openclaw/deepseek-provider). The @kansodata/ scope here is a third-party vendor scope.

The guideline also states:

"Exceptions must be explicit and covered by the repo invariant test."

This PR includes neither an updated invariant test nor an explicit in-repo exception for @kansodata/databricks. If the check-additional CI architecture gate enforces the naming invariant, this PR will fail.

Either rename the package to @openclaw/databricks (the expected form for first-party workspace plugins) or add a documented, test-covered exception before landing.

Suggested change
"name": "@kansodata/databricks",
"name": "@openclaw/databricks",
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/databricks/package.json
Line: 2

Comment:
**Package scope violates the repo naming convention**

`CLAUDE.md` / `AGENTS.md` explicitly requires that all repo-owned workspace plugins use the `@openclaw/<id>` scope (or an approved suffix form like `-provider`, `-plugin`, etc.). Every other extension in this repo follows that convention (e.g. `@openclaw/open-prose`, `@openclaw/deepseek-provider`). The `@kansodata/` scope here is a third-party vendor scope.

The guideline also states:

> "Exceptions must be explicit and covered by the repo invariant test."

This PR includes neither an updated invariant test nor an explicit in-repo exception for `@kansodata/databricks`. If the `check-additional` CI architecture gate enforces the naming invariant, this PR will fail.

Either rename the package to `@openclaw/databricks` (the expected form for first-party workspace plugins) or add a documented, test-covered exception before landing.

```suggestion
  "name": "@openclaw/databricks",
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread extensions/databricks/package.json Outdated
@@ -0,0 +1,12 @@
{
"name": "@kansodata/databricks",
"version": "0.0.1",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Version does not follow the repo date-based convention

All existing extension packages use a date-based version string (e.g. "2026.3.27"). 0.0.1 is inconsistent with this pattern and may cause confusion or break any tooling that expects the canonical versioning format.

Suggested change
"version": "0.0.1",
"version": "2026.3.28",
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/databricks/package.json
Line: 3

Comment:
**Version does not follow the repo date-based convention**

All existing extension packages use a date-based version string (e.g. `"2026.3.27"`). `0.0.1` is inconsistent with this pattern and may cause confusion or break any tooling that expects the canonical versioning format.

```suggestion
  "version": "2026.3.28",
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread .github/labeler.yml
Comment on lines +244 to 248
"extensions: databricks":
- changed-files:
- any-glob-to-any-file:
- "extensions/databricks/**"
"extensions: anthropic":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Labeler entry placed out of alphabetical order

The new "extensions: databricks" entry is inserted after "extensions: deepseek", but databricks (d-a-t-a…) sorts before deepseek (d-e-e-p…) alphabetically. The surrounding entries follow alphabetical ordering. Move this block before the "extensions: deepseek" entry to stay consistent.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/labeler.yml
Line: 244-248

Comment:
**Labeler entry placed out of alphabetical order**

The new `"extensions: databricks"` entry is inserted after `"extensions: deepseek"`, but `databricks` (d-a-t-a…) sorts before `deepseek` (d-e-e-p…) alphabetically. The surrounding entries follow alphabetical ordering. Move this block before the `"extensions: deepseek"` entry to stay consistent.

How can I resolve this? If you propose a fix, please make it concise.

@Kansodata Kansodata changed the title feat(extensions): add @kansodata/databricks skill pack scaffold feat(extensions): add databricks skill pack scaffold Mar 28, 2026
@Kansodata

Copy link
Copy Markdown
Contributor Author

Gracias por el feedback. Ya quedó abordado en el último commit de este PR:

  • se cambió el paquete a @openclaw/databricks (se removió @kansodata)
  • se alineó version a 2026.3.27
  • se reordenó .github/labeler.yml para mantener orden alfabético
  • se regeneraron baselines de metadata (src/plugins/bundled-plugin-metadata.generated.ts y src/generated/bundled-plugin-entries.generated.ts)

Validación local ejecutada:

  • pnpm exec vitest run src/plugins/bundled-plugin-naming.test.ts
  • pnpm exec vitest run src/plugins/bundled-plugin-metadata.test.ts

@Kansodata
Kansodata force-pushed the feat/kansodata-databricks-skill-pack branch from d9dce15 to a33f5f1 Compare March 28, 2026 20:47

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a33f5f136e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

## Install

```bash
openclaw plugins install @openclaw/databricks

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fix Databricks install docs for unpublished package

The README currently tells users to run openclaw plugins install @openclaw/databricks, but this package is not publishable as committed (extensions/databricks/package.json sets private: true and does not opt into release publishing), so npm-based installs will fail for normal users outside this repo. This creates a broken setup path in the new plugin docs; either publish metadata needs to be added (see collectPublishablePluginPackages in scripts/lib/plugin-npm-release.ts) or the README should be changed to the bundled-enable flow.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 913feb9881

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/databricks/src/client.ts Outdated
}
throw error;
}
return payload;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Poll statement status before returning SQL results

The client returns immediately on any HTTP 2xx response, but Databricks statement submission can return a non-terminal status.state (for example PENDING/RUNNING) with only a statement_id. In that case this tool exits without result rows and, because there is no follow-up GET /api/2.0/sql/statements/{statement_id} path in this plugin, users cannot retrieve completion data for slower queries. This makes the runtime unreliable for many real queries that do not finish within the initial submit response window.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 66ddb16591

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +79 to +83
assertAllowlistTarget({
allowedCatalogs: config.allowedCatalogs,
allowedSchemas: config.allowedSchemas,
catalog,
schema,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce allowlists against SQL text, not caller hints

The allowlist gate here validates only the optional catalog/schema parameters, but those values are caller-controlled and never checked against the actual SQL statement. If allowedCatalogs/allowedSchemas are configured, a request can still pass validation by supplying an allowlisted hint while querying a fully-qualified table in a different catalog/schema, which bypasses the intended fail-closed data-scope control.

Useful? React with 👍 / 👎.

Comment on lines +297 to +300
const status = getStatementStatus(initialPayload);
if (!isPendingStatus(status)) {
return initialPayload;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Throw on FAILED/CANCELED statement terminal states

resolveFinalStatementPayload treats every non-pending state as success, so a Databricks response with status.state of FAILED or CANCELED is returned as a normal tool result instead of an error. This causes failed SQL executions to look successful to callers and skips normal failure handling paths (for example retries/fallback logic that depends on tool errors).

Useful? React with 👍 / 👎.

@Kansodata

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback. This has been addressed in the latest commit of this PR:

  • updated the package to @openclaw/databricks (removed @kansodata)
  • aligned version to 2026.3.27
  • reordered .github/labeler.yml to keep alphabetical order
  • regenerated metadata baselines (src/plugins/bundled-plugin-metadata.generated.ts and src/generated/bundled-plugin-entries.generated.ts)

Local validation executed:

  • pnpm exec vitest run src/plugins/bundled-plugin-naming.test.ts
  • pnpm exec vitest run src/plugins/bundled-plugin-metadata.test.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d30526ed7c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const sanitized = stripSqlCommentsAndLiterals(rawSql);
const references = new Set<string>();
const targets: ResolvedSqlTarget[] = [];
const pattern = /\b(?:from|join)\s+((?:[A-Za-z_][\w$]*)(?:\s*\.\s*[A-Za-z_][\w$]*){1,2})\b/giu;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Parse comma-joined tables before enforcing allowlists

resolveSqlTargets only extracts relations that immediately follow FROM or JOIN, so a query like SELECT ... FROM main.public.ok, secret.finance.payroll yields targets for only the first table. Because assertAllowlistTarget accepts any non-empty target list and validates only extracted targets, this lets disallowed catalog/schema references slip through when allowlists are enabled but at least one allowlisted table is also present. This is a fail-open path in the data-scope control and should be handled by parsing comma-separated FROM entries (or rejecting unsupported FROM forms when allowlists are active).

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 41ca95b425

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +342 to +343
if (!parsed.target) {
continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fail closed when any FROM/JOIN target is unresolved

The resolver currently drops unresolved FROM/JOIN identifiers (if (!parsed.target) continue), which means allowlist enforcement can be bypassed whenever at least one allowlisted table is also present. For example, SELECT ... FROM main.public.orders o JOIN payroll p ... yields only main.public.orders, so assertAllowlistTarget passes even though payroll is an ambiguous/default-schema table outside explicit scope. This is a fail-open data-scope issue; unresolved relation targets need to be treated as unsafe when allowlists are active.

Useful? React with 👍 / 👎.

@Kansodata
Kansodata force-pushed the feat/kansodata-databricks-skill-pack branch from 41ca95b to 71b0c0a Compare March 31, 2026 15:53
@aisle-research-bot

aisle-research-bot Bot commented Mar 31, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 4 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Allowlist bypass via comma-separated FROM sources not resolved by SQL target extractor
2 🟠 High SSRF and Databricks token exfiltration via configurable host
3 🟡 Medium Unbounded Databricks SQL query execution and inline result return can cause resource exhaustion
4 🟡 Medium Databricks SQL statements may continue running after client timeout/polling timeout (no cancellation)
1. 🟠 Allowlist bypass via comma-separated FROM sources not resolved by SQL target extractor
Property Value
Severity High
CWE CWE-284
Location extensions/databricks/src/sql-target-resolution.ts:318-345

Description

resolveSqlTargets is used to enforce catalog/schema allowlists for the databricks_sql_readonly tool. However, the resolver only extracts targets that immediately follow FROM or JOIN keywords.

This misses additional table references expressed via comma-separated FROM lists (implicit cross joins), e.g.:

SELECT *
FROM allowed.schema_table a, disallowed.secret_schema.secret_table s
WHERE a.id = s.id

In this case:

  • resolveSqlTargets will resolve only allowed.schema_table (the first identifier after FROM)
  • it will not resolve disallowed.secret_schema.secret_table because it is not preceded by FROM/JOIN
  • assertAllowlistTarget will be satisfied (because it sees only allowlisted targets), but Databricks will still read from the disallowed table

This creates a policy bypass for configured allowlists.

Recommendation

Treat any table source in the FROM clause as a potential target, not only the first token after FROM/JOIN.

Minimum fix (to cover the bypass): after resolving the first FROM target, continue scanning the same FROM clause for comma-separated sources and extract any subsequent qualified identifiers, failing closed on unsupported constructs.

For example, extend the loop to also parse , <qualified_identifier> sequences after a FROM target until a clause boundary (e.g. WHERE, GROUP, HAVING, ORDER, LIMIT, etc.), and add tests:

// Pseudocode sketch
if (token.value === "FROM") {
  let cursor = index + 1;
  while (cursor < tokens.length) {// parse one source (qualified identifier or subquery)// ...// if next token is ',', advance and parse another source
    if (tokens[cursor]?.kind === "symbol" && tokens[cursor].value === ",") {
      cursor += 1;
      continue;
    }
    break;
  }
}

Also add an explicit regression test such as:

expect(resolveSqlTargets("select * from sales.orders o, private.secrets s")).toEqual([
  { schema: "sales", table: "orders", raw: "sales.orders" },
  { schema: "private", table: "secrets", raw: "private.secrets" },
]);

If comprehensive SQL parsing is out of scope, an alternative is to fail closed when a comma appears in a FROM clause while allowlists are enabled (reject the query), because comma-joins indicate multiple sources that must be validated.

2. 🟠 SSRF and Databricks token exfiltration via configurable `host`
Property Value
Severity High
CWE CWE-918
Location extensions/databricks/src/client.ts:152-175

Description

The Databricks plugin constructs outbound HTTPS requests using a configurable host value and always attaches the configured Databricks bearer token in the Authorization header.

  • host can be supplied from plugin config (plugins.entries.databricks.config.host) or environment (DATABRICKS_HOST).
  • normalizeHost() enforces https: and strips path/query/fragment, but does not restrict the hostname (any domain/IP is accepted) and does not explicitly strip username:password@ userinfo.
  • DatabricksSqlClient.executeStatement() (and polling) sends Authorization: Bearer <token> to ${config.host}/api/....

If an attacker can influence the plugin configuration or environment (e.g., in a multi-tenant deployment, compromised config channel, or untrusted runtime overrides) while a privileged token is resolved from a secret store/env, they can set host to an attacker-controlled HTTPS endpoint and cause the runtime to exfiltrate the Databricks token and perform arbitrary outbound requests (SSRF).

Vulnerable code (host normalization + request construction):

// config.ts
return url.toString().replace(/\/+$/u, "");// client.ts
const endpoint = `${this.config.host}/api/2.0/sql/statements`;
headers: { Authorization: `Bearer ${this.config.token}` }

Recommendation

Constrain and sanitize the host value before any request is made.

Minimum hardening:

  1. Strip userinfo explicitly and reject if present.
  2. Reject IP literals and/or private/reserved ranges to reduce SSRF risk.
  3. Allowlist expected Databricks domains (or require an explicit operator-managed allowlist).

Example:

function normalizeHost(rawHost: string): string {
  const prefixed = /^https?:\/\//i.test(rawHost) ? rawHost : `https://${rawHost}`;
  let url: URL;
  try { url = new URL(prefixed); } catch { throw new DatabricksConfigError("Invalid databricks host URL."); }

  if (url.protocol !== "https:") throw new DatabricksConfigError("Databricks host must use HTTPS.");
  if (url.username || url.password) throw new DatabricksConfigError("Databricks host must not include credentials.");
  if (url.pathname && url.pathname !== "/") throw new DatabricksConfigError("Databricks host must not include a path.");// Optional but recommended: allowlist known Databricks host suffixes.
  const hostname = url.hostname.toLowerCase();
  const allowedSuffixes = [".databricks.com"]; // extend for your deployment
  if (!allowedSuffixes.some((suffix) => hostname === suffix.slice(1) || hostname.endsWith(suffix))) {
    throw new DatabricksConfigError("Databricks host must be a databricks.com domain.");
  }

  url.pathname = "";
  url.search = "";
  url.hash = "";
  return url.toString().replace(/\/+$/u, "");
}

If your product must support arbitrary/custom Databricks domains, add an operator-controlled allowedHosts/allowedHostSuffixes list and validate against that (not user-provided tool inputs).

3. 🟡 Unbounded Databricks SQL query execution and inline result return can cause resource exhaustion
Property Value
Severity Medium
CWE CWE-400
Location extensions/databricks/src/operations/sql.ts:63-117

Description

The databricks_sql_readonly tool accepts an arbitrary read-only SELECT statement and executes it against a Databricks SQL Warehouse without enforcing any row/result-size budget (e.g., requiring/adding a LIMIT) and returns the full statement payload to the caller.

Impact:

  • A prompt-injected agent/user can run large table scans / expensive joins (even if read-only), causing cost spikes and warehouse resource exhaustion.
  • The client requests disposition: "INLINE", encouraging Databricks to include result data in the response; the tool then returns the entire payload via jsonResult, which can create large gateway memory/serialization pressure.

Vulnerable code paths:

  • Input: sql parameter from tool call
  • Sink: client.executeStatement({ statement: safeSql, ... })
  • Output: jsonResult({ ..., response: payload })

Recommendation

Enforce a result/cost budget at the tool boundary.

Options (prefer combining more than one):

  1. Require or inject a LIMIT (and/or a maximum bytes/rows policy) unless the query already has a safe limit.
const MAX_ROWS = 1000;
const safeSql = assertReadOnlySqlStatement(sql);

if (!/\bLIMIT\b/i.test(stripSqlCommentsAndLiterals(safeSql))) {
  throw new DatabricksPolicyError(`Query must include LIMIT <= ${MAX_ROWS}.`);
}
  1. Avoid INLINE disposition for untrusted queries; use EXTERNAL_LINKS (or equivalent) and download only up to a bounded number of chunks/bytes.

  2. Add an explicit max response size guard before returning payload (truncate/omit large fields).

  3. Add org-level controls: statement execution quotas, per-tool rate limiting, and warehouse-level query limits.

4. 🟡 Databricks SQL statements may continue running after client timeout/polling timeout (no cancellation)
Property Value
Severity Medium
CWE CWE-400
Location extensions/databricks/src/client.ts:163-210

Description

DatabricksSqlClient enforces request/polling time budgets using AbortController and local timers. However, when a timeout occurs (or polling exceeds the configured max wait), the code throws an error without issuing a cancellation request to Databricks.

Impact:

  • An attacker (or prompt-injected agent) can submit expensive read-only queries that continue running in Databricks even after this plugin gives up, causing unexpected ongoing compute cost and potential warehouse saturation.

Why this is grounded in code:

  • AbortController only aborts the HTTP request from this service; it does not cancel the already-submitted statement in Databricks.
  • On timeout, the code throws STATEMENT_TIMEOUT immediately, with no follow-up call to cancel the statement ID.

Recommendation

On timeout / polling budget exhaustion, attempt to cancel the running statement in Databricks.

Example approach:

  1. Capture statement_id from the initial POST /api/2.0/sql/statements response (even if you plan to poll later).
  2. If a timeout occurs after statement submission, call the cancel endpoint (Databricks Statements API) before throwing.
async cancelStatement(statementId: string) {
  await this.fetchImpl(`${this.config.host}/api/2.0/sql/statements/${encodeURIComponent(statementId)}/cancel`, {
    method: "POST",
    headers: { Authorization: `Bearer ${this.config.token}` },
  });
}// On timeout/polling timeout:
await this.cancelStatement(statementId);
throw new DatabricksError({ code: "STATEMENT_TIMEOUT", ... });

Also consider server-side safeguards: warehouse query timeouts, statement limits, and per-tool quotas.


Analyzed PR: #56592 at commit 6a162b2

Last updated on: 2026-03-31T17:02:52Z

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 71b0c0a808

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +129 to +133
return new DatabricksError({
code: "UNKNOWN_ERROR",
message: error.message || fallbackMessage,
retryable: false,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Classify transient fetch failures as retryable

When fetch throws a transport-level error (for example DNS/connect reset/temporary socket failures), this branch maps it to UNKNOWN_ERROR with retryable: false, so both executeStatement and polling fail immediately instead of honoring configured retries. In Node/Undici these transient failures are commonly surfaced as non-AbortError exceptions, so this causes avoidable query failures during brief network instability.

Useful? React with 👍 / 👎.

@Kansodata
Kansodata force-pushed the feat/kansodata-databricks-skill-pack branch from 71b0c0a to 1c5e119 Compare March 31, 2026 16:17
@openclaw-barnacle openclaw-barnacle Bot added channel: googlechat Channel integration: googlechat channel: slack Channel integration: slack labels Mar 31, 2026
@Kansodata
Kansodata force-pushed the feat/kansodata-databricks-skill-pack branch from 1c5e119 to 6a162b2 Compare March 31, 2026 16:19
@openclaw-barnacle openclaw-barnacle Bot removed the channel: slack Channel integration: slack label Mar 31, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6a162b26dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"REVOKE",
"CALL",
"COPY",
"REPLACE",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove REPLACE from mutating keyword denylist

assertNoMutatingKeyword applies this list to all SQL text, so including REPLACE causes legitimate read-only statements such as SELECT REPLACE(col, 'a', 'b') FROM ... to be rejected with a policy violation. In this plugin’s only supported mode (SELECT/WITH ... SELECT), that creates false positives for common analytics queries and unnecessarily blocks valid usage without improving write-safety.

Useful? React with 👍 / 👎.

Kansodata and others added 5 commits March 31, 2026 13:42
# Conflicts:
#	src/generated/bundled-plugin-entries.generated.ts
#	src/plugins/bundled-plugin-metadata.generated.ts
# Conflicts:
#	src/plugins/bundled-plugin-metadata.generated.ts
# Conflicts:
#	src/plugins/bundled-plugin-metadata.generated.ts
@Kansodata
Kansodata force-pushed the feat/kansodata-databricks-skill-pack branch from 2eb7603 to f29baff Compare March 31, 2026 16:43
@openclaw-barnacle openclaw-barnacle Bot removed the channel: googlechat Channel integration: googlechat label Mar 31, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f29baff42e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +204 to +209
if (normalized.code === "TIMEOUT") {
throw new DatabricksError({
code: "STATEMENT_TIMEOUT",
message: normalized.message,
retryable: normalized.retryable,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Retry timed-out statement submissions before failing

This timeout branch throws immediately, so retryCount is never applied when the initial SQL submission aborts. As a result, transient timeout conditions (for example brief network stalls where a second attempt would succeed) always fail the tool call on the first timeout instead of using the configured retry budget, which makes production query execution less reliable under intermittent connectivity.

Useful? React with 👍 / 👎.

@sallyom

sallyom commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Thanks for this contribution. We’re moving new plugins like this to ClawHub instead of openclaw/openclaw core. Please consider resubmitting it there:

@sallyom sallyom closed this Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants