feat(ai-proxy): add Snowflake integration with PAT authentication#1573
feat(ai-proxy): add Snowflake integration with PAT authentication#1573christophebrun-forest wants to merge 3 commits intomainfrom
Conversation
Exposes 3 MCP tools backed by Snowflake REST API v2 (Cortex Search, Cortex Analyst, read-only SQL execution), authenticated via Programmatic Access Tokens. The execute-query tool enforces a defense-in-depth read-only SQL guard on top of Snowflake role privileges. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 new issues
|
|
Coverage Impact ⬆️ Merging this pull request will increase total coverage on Modified Files with Diff Coverage (6) 🤖 Increase coverage with AI coding...🚦 See full report on Qlty Cloud » 🛟 Help
|
The previous regex pipeline stripped comments before string literals, so attacker-crafted statements like `SELECT '--' DELETE FROM users` had the forbidden `DELETE` consumed as part of a "line comment" and the guard accepted the query. SQL is context-sensitive: a `--` may live inside a string, and a `'` may live inside a comment. A regex pipeline cannot disambiguate the two in any order. Replace it with a single-pass lexer that tracks string/identifier/comment state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| name: 'snowflake_execute_query', | ||
| description: | ||
| 'Execute a read-only SQL query on Snowflake and return the results. ' + | ||
| 'Only SELECT, WITH, SHOW, DESCRIBE, and EXPLAIN statements are allowed. ' + |
There was a problem hiding this comment.
WITH is in FORBIDDEN_KEYWORD_RE, so it should be removed from this description.
|
|
||
| export function getSnowflakeValidationBaseUrl(accountIdentifier: string): string { | ||
| return `https://${accountIdentifier}.snowflakecomputing.com`; | ||
| } |
There was a problem hiding this comment.
accountIdentifier is interpolated into the URL without validation — accountIdentifier: "victim.com#" would route the bearer token to victim.com. Should validate against /^[A-Za-z0-9_-]+(?:[.-][A-Za-z0-9_-]+)*$/ at config time.
Maybe we can do this later and for zendesk mcp too
There was a problem hiding this comment.
Add assertValidAccountIdentifier a control function in utils.ts. We can then look into creating a global method that can be reused in Zendesk, Snowflake, etc..
|
|
||
| export function buildSnowflakeBaseUrl(config: SnowflakeConfig): string { | ||
| return `https://${config.accountIdentifier}.snowflakecomputing.com`; | ||
| } |
There was a problem hiding this comment.
getSnowflakeValidationBaseUrl and buildSnowflakeBaseUrl return the same URL — could collapse into one helper.
There was a problem hiding this comment.
delete buildSnowflakeBaseUrl
rename getSnowflakeValidationBaseUrl to getSnowflakeBaseUrl
Use this method in tools.ts
- Validate accountIdentifier against a strict regex before interpolating it into the base URL. Without this, a value like `attack.com#` made the URL resolve to `https://attack.com` (the `#` opens the URL fragment), redirecting validation and tool calls to attacker-controlled hosts. - Consolidate getSnowflakeValidationBaseUrl and buildSnowflakeBaseUrl (now identical) into a single getSnowflakeBaseUrl in utils.ts. - Drop WITH from the snowflake_execute_query description: WITH is in FORBIDDEN_KEYWORD_RE and the description was misleading the LLM. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Summary
Adds a new Forest integration in
@forestadmin/ai-proxythat exposes 3 MCP tools backed by Snowflake's REST API v2, authenticated via Programmatic Access Tokens (PAT).Tools shipped:
snowflake_cortex_search— semantic search via a Cortex Search service (caller passesdatabase/schema/serviceas arguments).snowflake_cortex_analyst— natural-language analytical Q&A backed by a semantic model file (@stage/model.yaml) or a semantic view (XOR validated at runtime).snowflake_execute_query— synchronous SQL execution restricted to read-only statements.Authentication: PAT bearer token (
Authorization: Bearer <pat>+X-Snowflake-Authorization-Token-Type: PROGRAMMATIC_ACCESS_TOKEN). The token is passed per-request via the integration config — no shared OAuth flow.Config shape:
```ts
{
accountIdentifier: string; // e.g. "myorg-myaccount" or account locator
programmaticAccessToken: string;
defaultWarehouse?: string;
defaultDatabase?: string;
defaultSchema?: string;
defaultRole?: string;
}
```
Read-only SQL guard (defense-in-depth):
Test plan
🤖 Generated with Claude Code
Note
Add Snowflake integration with PAT authentication to ai-proxy
Snowflakeintegration toForestIntegrationClient, wiring uploadToolsandcheckConnectionalongside existing integrations.DynamicStructuredToolinstances:snowflake_cortex_search,snowflake_cortex_analyst, andsnowflake_execute_query, each calling Snowflake's REST API with Programmatic Access Token (PAT) auth headers.snowflake_execute_queryenforces read-only SQL by rejecting mutating keywords and multi-statement queries before any network call viaassertReadOnlySqlin utils.ts.validateSnowflakeConfigperforms a live connectivity check by executingSELECT 1against the Snowflake statements API, throwingMcpConnectionErroron failure.normalizeSqlin utils.ts contains a reported syntax error (stray leading+) that may cause a load-timeSyntaxErrorin the utils module.Changes since #1573 opened
AIBadRequestErrorfor invalid Snowflake account identifiers [534eaae]Macroscope summarized 7d4ba41.