title: Agent-Generated SQL Reaches Outside the Database via COPY TO PROGRAM or a Server-Side File Read
id: ATR-2026-02703
rule_version: 2
status: experimental
description: |
  Detects SQL, produced by the model or carried in an agent event, that uses a database engine's escape hatches into the host: PostgreSQL `COPY ... TO PROGRAM '<cmd>'` (pipes a result set into a shell command), and `pg_read_file` / `pg_read_binary_file` / `pg_stat_file` / `lo_import` called on a path that traverses out of the data directory or names a credential file. Mined from GHSA-pmch-g965-grmr (Langroid SQLChatAgent), whose `_validate_query` blocklist stopped the obvious DML verbs and missed the `pg_read_file` family entirely, turning a natural-language question into an arbitrary server-side file read. ATR already covers the ingest direction -- ATR-2026-01987 matches `COPY ... FROM PROGRAM` and `INTO OUTFILE` -- but verified on the shipped engine, the exfiltration direction (`COPY orders TO PROGRAM 'nc ...'`) and the whole pg_read_file family match nothing.
  ADVERSARIAL REVIEW 2026-08-24 -- THREE CORRECTIONS, ALL MEASURED. Sixteen benign samples were run through the shipped engine against rule_version 1 across llm_input, llm_output, tool_call, tool_response and mcp_exchange. ALL SIXTEEN FIRED. What changed: (1) THE ABSOLUTE-PATH HANDLE WAS WRONG. Version 1 claimed an absolute path separates attack from DBA work. It does not: members of pg_read_server_files legitimately pass absolute paths, and pg_read_file('/var/log/postgresql/postgresql-16-main.log', 0, 200000) and pg_stat_file('/etc/postgresql/16/main/postgresql.conf') are ordinary operations that both fired. The handle is now the TARGET, not the shape: a traversal, or a named credential file (.pgpass, .ssh/, /etc/passwd, /etc/shadow, .aws/credentials, .env, authorized_keys, /proc/.../environ). lo_import was narrowed the same way -- version 1 fired on the migration SELECT lo_import('/opt/acme/assets/logo.png'). (2) TO PROGRAM NOW REQUIRES THE QUOTE THE SYNTAX REQUIRES. Version 1's bare word-boundary form fired on every Windows install instruction of the shape "copy the signed binary to Program Files/Acme/bin" -- six of the sixteen benign samples, including a CI build log, a changelog, an Ansible task and a Traditional Chinese deployment runbook. COPY ... TO PROGRAM is always followed by a quoted command string, so requiring it costs no recall. (3) THE OPENDATASOURCE ARM WAS REMOVED, NOT NARROWED. OPENDATASOURCE( is ordinary T-SQL: it is how a DBA pulls a one-off extract from an Excel file or another instance, and it is also the function a hardening checklist names when telling you to switch Ad Hoc Distributed Queries off. Both fired. No precision handle survives inspection: requiring an inline Password= does not work, because Microsoft's own OPENDATASOURCE reference example is 'Data Source=Seattle1;User ID=MyUID;Password=MyPass'. An arm that cannot separate the attack from the vendor's documentation of the feature is worse than no arm, so SQL Server ad-hoc distributed queries are now a stated non-goal of this rule.
author: ATR Community (GHSA sweep)
date: 2026/08/23
schema_version: '0.1'
detection_tier: pattern
maturity: test
severity: high
references:
  cwe:
    - CWE-89
    - CWE-73
  owasp_llm:
    - LLM06:2025
  owasp_agentic:
    - ASI05:2026
  mitre_attack:
    - T1005 - Data from Local System
  mitre_atlas:
    - AML.T0057 - LLM Data Leakage
  external:
    - https://github.com/advisories/GHSA-pmch-g965-grmr
metadata_provenance:
  cwe: human-reviewed
  owasp_llm: human-reviewed
  owasp_agentic: human-reviewed
  mitre_attack: human-reviewed
  mitre_atlas: human-reviewed
compliance:
  eu_ai_act:
    - article: '15'
      context: Article 15 (accuracy, robustness and cybersecurity) requires protection against unauthorised access to data; this rule detects agent-generated SQL that reads host files or pipes query results into a shell command.
      strength: primary
    - article: '10'
      context: Article 10 (data and data governance) expects data handled by the system to stay within its governed scope; a COPY TO PROGRAM or pg_read_file call removes it from that scope.
      strength: secondary
  nist_ai_rmf:
    - subcategory: MG.2.3
      context: Server-side file access through model-generated SQL is an identified AI risk requiring an active runtime countermeasure; this rule implements it.
      strength: primary
    - subcategory: MP.5.1
      context: Cataloguing SQL blocklist bypass through non-DML file primitives as an AI-specific risk in the organizational risk register.
      strength: secondary
  iso_42001:
    - clause: '8.1'
      context: ISO/IEC 42001 Clause 8.1 (operational planning and control) is operationalised by inspecting generated SQL for host-reaching primitives before execution.
      strength: primary
    - clause: '8.3'
      context: ISO/IEC 42001 Clause 8.3 (AI risk treatment) is implemented through runtime detection of the escape-hatch query.
      strength: secondary
tags:
  category: context-exfiltration
  subcategory: sql-host-escape-primitive
  scan_target: runtime
  confidence: high
agent_source:
  type: llm_io
  framework:
    - any
  provider:
    - any
detection:
  condition: any
  conditions:
    - field: content
      operator: regex
      value: (?:\bcopy\b[^;\r\n]{0,40}\bto\s{1,4}program\s{0,4}["\x27]|\b(?:pg_(?:read_file|read_binary_file|stat_file)|lo_import)\s{0,3}\(\s{0,3}["\x27][^"\x27\r\n]{0,90}(?:\.\./|/etc/(?:passwd|shadow|sudoers|ssh/)|\.pgpass|\.ssh/|authorized_keys|/proc/[\w./]{0,24}environ|/root/|\.aws/credentials|\.env\b|\.git-credentials))
      description: COPY ... TO PROGRAM followed by the quoted command string the syntax requires, or a pg_read_file/pg_stat_file/lo_import call whose path argument traverses or names a credential file. NARROWED by adversarial review 2026-08-24 -- see false_positives.
  false_positives:
    - 'Routine DBA file access, relative or absolute: pg_read_file(''pg_hba.conf''), pg_read_file(''/var/log/postgresql/postgresql-16-main.log'', 0, 200000), pg_stat_file(''/etc/postgresql/16/main/postgresql.conf''), lo_import(''/opt/acme/assets/logo.png''). Version 1 fired on the last three; the condition now requires a traversal or a named credential target.'
    - SQL Server ad-hoc distributed queries. OPENDATASOURCE( is no longer matched at all -- see correction (3) in the description. Separating linked-server abuse from a legitimate ad-hoc extract needs configuration state (is Ad Hoc Distributed Queries even enabled, and who is the caller) that a content regex does not have.
    - 'KNOWN AND UNFIXABLE, MEASURED: security writing that quotes the payload verbatim still fires -- a GHSA write-up containing pg_read_file(''/etc/passwd''), an incident postmortem containing COPY (SELECT * FROM invoices) TO PROGRAM ''curl -T - http://...'', this rule''s own description, and a pytest that asserts the validator REJECTS agent.run_sql("SELECT pg_read_file(''/etc/passwd'')"). Four of the sixteen benign samples are in this class and all four still fire. A content rule cannot tell a quoted payload from the payload, which is why the response is alert-only. Prose that DISCUSSES the technique without the executable string is silent, and that was measured.'
    - Any English, Chinese or shell instruction to copy something to a Program Files or program directory, because TO PROGRAM must be followed by the opening quote of the command string.
    - COPY ... FROM PROGRAM and SELECT ... INTO OUTFILE, which are matched by ATR-2026-01987 and deliberately left out here to avoid duplicate alerting on the ingest direction.
    - An ORM or migration file naming a column or function `lo_import` or `copy_to_program` without the call syntax and the required argument shape.
response:
  actions:
    - alert
  message_template: |
    [ATR-2026-02703] Generated SQL uses a database escape hatch to reach the host filesystem or a shell (COPY TO PROGRAM / pg_read_file on an absolute path / lo_import / OPENDATASOURCE). Validate generated SQL with an allowlist of statements, not a blocklist of verbs.
confidence: 80
