Skip to content
VediramVediramSupport

Database Access

Let a sandboxed agent query your databases over ODBC without ever holding a connection string or credential

Database access lets the agent query your databases without ever holding a password or connection string. By default the agent can reach no database. IT maps a short name (an alias) to a data source that already exists on the computer, and decides which statements and tables the agent may use. The agent writes ordinary SQL; Levee checks each statement, runs it as the signed-in user, and records it in the audit trail.

What does the agent see?

An ordinary database driver and data source names such as levee-sales. It never sees the connection string, the user name, the password, or any data source it was not granted. Each query is checked against policy and then run outside the sandbox, under the signed-in user's own database identity, through a data source the user already has.

Two consequences matter for your security reviewers:

  • Policy can only narrow access, never widen it. The user's existing data sources and the database's own permissions are the ceiling. A Levee rule cannot give the agent more than the user could already do.
  • Levee does not store database credentials. Sign-in uses whatever the data source already uses (Windows integrated authentication, a stored credential, and so on).

How does a query flow?

StepWhereWhat happens
1SandboxThe agent connects to a Levee data source by alias, for example levee-sales.
2ComputerLevee maps the alias to a real host DSN named in policy. An unknown alias is refused.
3ComputerEach statement is classified and checked against the alias rule (verbs, tables, dangerous patterns).
4ComputerThe decision is written to the audit trail before the query runs. If the audit record cannot be accepted, the query is refused.
5ComputerThe query runs through the host DSN with the rule's row cap and timeout, and the rows are returned to the agent.

For your IT team: prerequisites on the computer

You need a data source on the computer for each database, a database login with only the rights the agent should have, and a policy rule.

  1. A 64-bit ODBC data source for each database you want to expose, created for the Windows user who runs Levee. Any database with a 64-bit ODBC driver works: SQL Server, PostgreSQL, Oracle, MySQL, SQLite, Snowflake, and so on. Levee does not ship or require a particular database driver on the host.

    # Example: a per-user 64-bit DSN for SQL Server with Windows authentication
    Add-OdbcDsn -Name 'Sales-Prod' -DriverName 'ODBC Driver 18 for SQL Server' `
      -DsnType 'User' -Platform '64-bit' `
      -SetPropertyValue @('Server=sql01.corp.example', 'Database=Sales', 'Trusted_Connection=Yes', 'Encrypt=Yes')
    
    # Confirm it exists
    Get-OdbcDsn -Name 'Sales-Prod' -Platform '64-bit'
    
  2. A least-privilege database identity behind that DSN. See Scope the host DSN, not just the policy.

  3. A proxies.odbc policy section that maps an alias to the DSN. Without one, every connection is refused.

The Levee ODBC driver is already installed in the Levee container image. You do not install anything inside the sandbox.

Connecting from inside the sandbox

The agent connects by alias, with no password. The agent (or any tool it runs) connects with either form of connection string:

DSN=levee-<alias>
Driver={LeveeODBC};Alias=<alias>

For an alias sales, both DSN=levee-sales and Driver={LeveeODBC};Alias=sales connect to the host DSN mapped to sales.

  • Aliases may contain letters, digits, _ and -, up to 64 characters.
  • Use SQLDriverConnect (the connection-string form). The older SQLConnect call with a separate DSN, user and password is not supported and returns SQLSTATE IM001.
  • Anything after the alias (user, password, server) is ignored. The agent cannot redirect the connection.

A quick test from the container's PowerShell:

$conn = New-Object System.Data.Odbc.OdbcConnection 'DSN=levee-sales'
$conn.Open()
$cmd = $conn.CreateCommand()
$cmd.CommandText = 'SELECT TOP 5 * FROM dbo.Customers'
$reader = $cmd.ExecuteReader()
while ($reader.Read()) { $reader[0] }
$conn.Close()

Policy reference: proxies.odbc

Each rule names an alias, the data source behind it, and what the agent may do there. Database rules live in the proxies.odbc section of the Levee policy, either in %ProgramData%\Vediram\Levee\policy.json or in the ODBC section of a Portal policy. See How Levee Policy Works.

{
  "proxies": {
    "odbc": {
      "default": "deny",
      "rules": [
        {
          "alias": "sales",
          "hostDsn": "DSN=Sales-Prod",
          "allowedVerbs": ["SELECT"],
          "maxRowsPerQuery": 1000,
          "queryTimeoutSeconds": 10,
          "tables": { "allow": ["dbo.*"], "deny": ["dbo.CreditCards"] }
        },
        {
          "alias": "hr",
          "hostDsn": "DSN=HR-Reporting",
          "allowedVerbs": ["SELECT"],
          "tables": { "allow": ["*"], "deny": ["salary", "payroll.*"] }
        }
      ],
      "dangerousPatterns": ["sp_configure", "OPENQUERY"]
    }
  }
}

Top-level keys

KeyTypeDefaultMeaning
default"allow" or "deny"denyApplies to an alias with no rule. A missing, blank or misspelled value is treated as deny (with a warning in the log). Keep this at deny.
rulesarraynoneOne rule per alias.
dangerousPatternsstring arraybaseline listExtra patterns to block. They add to the built-in baseline and can never remove from it.

Rule keys

KeyTypeDefaultMeaning
aliasstringrequiredThe name the agent uses (levee-<alias>). Letters, digits, _, -; max 64 characters.
hostDsnstringrequiredThe host connection string, usually DSN=<your DSN name>. The agent never sees it.
allowedVerbsstring arraynoneStatement types permitted. If present, anything not listed is refused.
deniedVerbsstring arraynoneStatement types always refused. Checked before allowedVerbs.
tablesobjectall tables{ "allow": [...], "deny": [...] }. Only these two keys are accepted.
maxRowsPerQueryinteger10000Maximum rows returned for one statement.
queryTimeoutSecondsinteger30The query is terminated after this many seconds.

A malformed or non-positive maxRowsPerQuery or queryTimeoutSeconds falls back to the default and logs a warning.

Statement types (verbs)

Levee recognizes these verbs: SELECT, INSERT, UPDATE, DELETE, MERGE, CREATE, DROP, ALTER, TRUNCATE, EXEC (covers EXECUTE), GRANT, REVOKE, DENY.

  • A statement that starts with a WITH common table expression is classified by the statement that follows it (WITH ... SELECT is a SELECT).
  • Any statement Levee cannot classify is refused, whatever the policy says. That includes WAITFOR, BACKUP, USE, DECLARE, and statements wrapped in parentheses.

Table patterns

PatternMatches
*Every table
schema.*Every table in that schema
name or schema.nameThat table, case-insensitively
  • Deny always wins over allow.
  • An empty allow list means all tables are allowed (subject to deny).
  • A table is checked by both its bare name and its schema.name, so denying salary also covers hr.salary.

Dangerous patterns

These patterns are always blocked, even if your policy omits dangerousPatterns:

xp_cmdshell   OPENROWSET   OPENDATASOURCE   xp_regread   sp_oacreate   BULK INSERT

Add your own to extend the list. Matching ignores string literals and comments, so a pattern mentioned inside a quoted value does not trigger a block.

Validation behavior

Levee validates each rule when policy is applied:

  • With default: "deny", a malformed rule is skipped, so its alias simply has no access.
  • With default: "allow", a malformed rule is turned into deny-all for that alias. If Levee cannot even identify the alias, the whole update is rejected and the previous database policy stays in force. An allow default with no rules key is also rejected.
  • tables accepts only allow and deny. A misspelling such as denied makes the rule malformed.
  • Unknown rule keys (for example hostdsn or denyVerbs) are accepted but logged as warnings, and have no effect. Check the log after editing policy by hand.

How is a statement checked?

One statement at a time, against a fixed order of checks. The first check that refuses stops evaluation. Checks run in this order:

  1. More than one statement in the request is refused, including batches stacked without semicolons.
  2. Dangerous pattern found: refused.
  3. Unclassifiable statement: refused.
  4. No rule for the alias: default applies.
  5. Verb in deniedVerbs: refused.
  6. Verb not in allowedVerbs (when the list is present): refused.
  7. A referenced table is denied or not allowed: refused.
  8. Otherwise the statement is permitted, audited, and run with the rule's row cap and timeout.

The alias itself is also checked when the connection opens. An alias with no rule under a deny default cannot connect at all.

Can the agent see tables it may not query?

No. Catalog calls (SQLTables, SQLColumns) are subject to the same table rules. Tables the agent may not query do not appear in listings, so the agent cannot discover their names or columns through metadata.

What does the agent see when something is refused?

A normal database (ODBC) error, so the agent and its tools report it like any other database error. Common ones:

SQLSTATEMessageMeaning
08004Connection refused: unknown or unauthorized aliasNo rule for this alias, or it is not permitted.
08004Connection refused: audit unavailable (fail closed)The connection could not be recorded, so it was refused.
42000Multiple SQL statements in a single request are not allowedSend one statement per call.
42000Dangerous SQL pattern blocked: <pattern>A baseline or policy pattern matched.
42000Unrecognized SQL statement denied (verb could not be classified)Rewrite as a plain supported statement.
42000No policy rule for alias '<alias>' and default is denyAdd a rule for the alias.
42000Verb <VERB> is explicitly deniedThe verb is in deniedVerbs.
42000Verb <VERB> is not in allowed verbs [...]The verb is not in allowedVerbs.
42000Table '<t>' is denied by policy / Table '<t>' is not allowed by policyA table rule refused the statement.
42000No policy configured; query denied (fail closed)No database policy has reached the project yet.
HY000Query refused: audit channel unavailable (fail closed)The audit record could not be accepted.
HYT00Query timed out and was terminated by policy.queryTimeoutSeconds was reached.
HY000Query execution failed. See host logs for details.The database itself returned an error. Details are in the host log, not shown to the agent.
01000 (warning)Result set truncated by row-cap policy; not all rows were returnedmaxRowsPerQuery was reached.

Database errors are deliberately not passed through verbatim, so the agent cannot use error text to learn about the schema. See Database queries refused for fixes.

What happens when something is not available?

The query is refused. In detail:

  • No policy yet: when a project starts, connections, queries and metadata calls wait up to 30 seconds for the database policy to arrive, then are refused.
  • Levee service unavailable: if the project loses contact with the Levee service, database policy is dropped after a 45-second grace period and everything is refused until the service pushes policy again.
  • Audit unavailable: a connection is refused unless its audit record is confirmed stored; a query or metadata call is refused unless its audit record is accepted.

ODBC feature support

Plain SQL tools work; tools that rely on prepared or parameterized statements do not. The Levee driver is a 64-bit Unicode ODBC 3.8 driver. It reports the DBMS name LeveeODBC.

SupportedNot supported
SQLDriverConnect with DSN=levee-<alias> or Driver={LeveeODBC};Alias=<alias>SQLConnect (DSN, user, password form)
SQLExecDirect, SQLFetch, SQLGetData, SQLMoreResultsSQLPrepare, SQLExecute, SQLBindParameter (parameterized queries)
SQLDescribeCol, SQLColAttribute, SQLNumResultColsSQLBindCol
SQLTables, SQLColumns (filtered by policy)SQLPrimaryKeys, SQLStatistics, SQLGetTypeInfo
SQLGetInfo, SQLGetFunctions, diagnostics

Behavior worth knowing:

  • Rows arrive in one response, up to maxRowsPerQuery. Rows beyond the cap cannot be fetched; narrow the query instead.
  • SQLRowCount always returns -1, so affected-row counts for INSERT, UPDATE and DELETE are not available.
  • Transactions follow the host DSN's default (usually autocommit). SQLEndTran has no effect.
  • DECIMAL and NUMERIC values are returned as double-precision numbers, BIT as a small integer, and binary or unknown types as text.
  • Up to 20 simultaneous connections per alias.

Tools that rely on unsupported calls (for example, some ORMs that always prepare statements) will fail. Plain SQL through pyodbc cursor.execute(sql) without parameters, PowerShell System.Data.Odbc, and similar direct-execution clients work.

Audit

Every connection, statement and decision is recorded in the Levee audit trail. See Audit Event Reference.

EventRecorded when
odbc.connect / odbc.connect.deniedA connection is opened or refused
odbc.queryA statement passed policy and is about to run
odbc.query.deniedA statement was refused (the reason is included)
odbc.query.timeoutA statement hit queryTimeoutSeconds
odbc.security.dangerous_patternA dangerous pattern was matched
odbc.metadataA catalog call was made
odbc.disconnectA connection closed
odbc.policy.partial / odbc.policy.rejectedA policy update was partly or wholly rejected

Audit records include the SQL text (truncated to 500 characters), because auditors need to see what was run. Treat the audit store accordingly. The diagnostic log, by contrast, never contains SQL text or connection strings.

Scope the host DSN, not just the policy

Give the agent's data source a database login that can do no more than the rule allows. That is the strongest control.

Levee's statement checks are a policy guard, not a full SQL parser. They classify the leading statement and look for table names and known-dangerous constructs. That is effective against ordinary misuse, but a determined attacker can hide intent inside constructs a guard cannot fully understand, such as dynamic SQL built from string fragments.

The strongest control is the database's own permission system. For every hostDsn:

  • Use a database login that has no more privilege than the rule is meant to allow. For a read-only rule, use a login that can only SELECT from the intended schemas.
  • Do not point a Levee alias at a DSN that uses a DBA or owner account.
  • Prefer a dedicated reporting replica for exploratory agent work.

With that in place, the Levee rule and the database grant back each other up: the rule gives you fast, audited, human-readable control, and the database guarantees the ceiling.