# Security Considerations for Using `df.http()` in pg_durable and the `http-allow-azure-domains` Cargo Feature

> Secure your pg_durable deployments by understanding df.http() security. Learn how http-allow-azure-domains prevents SSRF by validating Azure domains.

- Repository: [Microsoft/pg_durable](https://github.com/microsoft/pg_durable)
- Tags: best-practices
- Published: 2026-06-08

---

**Using `df.http()` in pg_durable requires explicit role-based `EXECUTE` privileges, and the `http-allow-azure-domains` Cargo feature mitigates SSRF by blocking private IPs, bare IP addresses, and redirects while allowing only approved Azure service domains.**

The `pg_durable` extension enables durable workflows inside PostgreSQL, including the ability to make outbound HTTP calls via `df.http()`. Because this opens the database to the network, the Microsoft `pg_durable` repository implements a defense-in-depth security model combining compile-time feature flags, runtime privilege checks, and SSRF hardening. Understanding the security considerations for using `df.http()` and the `http-allow-azure-domains` feature is critical before granting production access.

## Why `df.http()` Is Opt-In by Default

On a fresh installation, the `EXECUTE` privilege on `df.http()` is **revoked from `PUBLIC`** in [`src/lib.rs`](https://github.com/microsoft/pg_durable/blob/main/src/lib.rs) (lines 540–560). This means no role can invoke the function until a database administrator explicitly grants access. The permission model is further specified in [`docs/spec-http-function-permissions.md`](https://github.com/microsoft/pg_durable/blob/main/docs/spec-http-function-permissions.md).

A DBA can grant permission using standard SQL:

```sql
GRANT EXECUTE ON FUNCTION df.http(text, text, text, jsonb, integer) TO analytics_user;

```

Alternatively, the helper function `df.grant_usage(role, include_http => true)` streamlines the process by granting both standard workflow privileges and HTTP access (see [`src/lib.rs`](https://github.com/microsoft/pg_durable/blob/main/src/lib.rs), lines 570–590). This opt-in design prevents accidental exposure of outbound network capabilities to untrusted database users.

## Compile-Time Feature Flags for HTTP Access

The available Cargo features determine which URLs `df.http()` may reach at build time. The behavior is controlled in [`src/ssrf.rs`](https://github.com/microsoft/pg_durable/blob/main/src/ssrf.rs) (lines 6–14), and human-readable guidance is documented in [`docs/http-security.md`](https://github.com/microsoft/pg_durable/blob/main/docs/http-security.md).

- **No feature flag** — All outbound HTTP is blocked at both DSL-time and execution-time.
- **`http-allow-azure-domains`** — The SSRF IP blocklist is active, bare IPs are blocked, redirects are blocked, and only Azure data-plane suffixes are allowed.
- **`http-allow-test-domains`** — Extends the Azure feature by also allowing `api.github.com` and `httpbingo.org` for end-to-end testing. This implies `http-allow-azure-domains`.
- **`http-allow-all`** — Disables all SSRF protections for development use only.

Production deployments should build with `http-allow-azure-domains` and never enable `http-allow-all`.

## SSRF Protections in [`src/ssrf.rs`](https://github.com/microsoft/pg_durable/blob/main/src/ssrf.rs)

The [`src/ssrf.rs`](https://github.com/microsoft/pg_durable/blob/main/src/ssrf.rs) file implements the core allow-list and SSRF defense logic that underpins the `http-allow-azure-domains` feature.

### IP Blocklist for Private and Loopback Addresses

The `check_blocked_ip` function (lines 76–94) rejects resolved addresses that fall into restricted ranges:

- IPv4 private ranges: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`
- IPv4 link-local: `169.254.0.0/16`
- IPv4 loopback: `127.0.0.0/8` and reserved `0.0.0.0/8`
- IPv6 loopback, unspecified, link-local, and unique-local (`fc00::/7`)

When `http-allow-all` is **not** enabled, this blocklist is enforced via `#[cfg(not(feature = "http-allow-all"))]`.

### Endpoint Allow-List Validation

When compiled with `http-allow-azure-domains` or `http-allow-test-domains`, the `validate_url_allowlist` function (lines 61–69) performs strict hostname checks:

1. **Extract the hostname** using `extract_host`, which stops at `/`, `?`, or `#` to prevent query or fragment bypasses.
2. **Reject any bare IP address** (IPv4 or IPv6).
3. **Match Azure suffixes** against the `AZURE_DOMAIN_SUFFIXES` list (lines 46–67). Every suffix begins with a leading dot, ensuring at least one sub-domain label is present.
4. If `http-allow-test-domains` is enabled, also allow exact matches from `TEST_EXACT_DOMAINS`.

If validation fails, the request aborts with an error such as:

```

Blocked: 'evil.com' is not in the allowed endpoint list. Only requests to approved Azure service domains are permitted.

```

### DNS-Rebinding Protection

Even allowed hostnames could resolve to internal addresses through DNS rebinding. The `SsrfSafeResolver` wraps the standard DNS resolver and filters results through `check_blocked_ip`. If all resolved addresses are blocked, the request fails with a message prefixed by **"Blocked:"** and containing the word **"restricted"** (lines 99–108).

The helper `is_ssrf_block_error` recognizes these messages, allowing consistent audit logging when workflows are denied.

## Runtime Privilege Enforcement

Before executing any network call, the executor in [`src/activities/execute_http.rs`](https://github.com/microsoft/pg_durable/blob/main/src/activities/execute_http.rs) verifies that the submitting role holds `EXECUTE` privilege on `df.http()` via `has_function_privilege`.

If the check fails, the workflow aborts immediately with:

```

Blocked: role 'alice' does not have EXECUTE privilege on df.http().

```

This design ensures that both **compile-time** domain restrictions and **runtime** role-based permissions must be satisfied before any outbound traffic is generated.

## Practical Deployment and Code Examples

### Granting HTTP Privileges to a Role

Use explicit `GRANT` statements or the helper function to enable HTTP access for a specific role:

```sql
-- Direct grant
GRANT EXECUTE ON FUNCTION df.http(text, text, text, jsonb, integer) TO analytics_user;

-- Helper function that also grants other workflow privileges
SELECT df.grant_usage('analytics_user', include_http => true);

```

These patterns are defined in [`src/lib.rs`](https://github.com/microsoft/pg_durable/blob/main/src/lib.rs) (lines 540–590).

### Building with `http-allow-azure-domains`

Compile the extension with the Azure allow-list feature enabled:

```bash
cargo build --release --features http-allow-azure-domains

```

The official `Dockerfile` (lines 9–10) already includes this flag, making it the default for production container images.

### Calling `df.http()` from a Workflow

Once privileges and features are configured, start a workflow that calls an allowed Azure endpoint:

```sql
SELECT df.start(
    df.http('https://myaccount.blob.core.windows.net/container/blob', 'GET')
);

```

If the calling role lacks `EXECUTE` on `df.http()`, the workflow fails with a clear "Blocked" error before any network packet is sent.

### Testing with the E2E Feature Flag

For CI pipelines or integration suites, enable the test domain feature:

```bash
cargo test --features http-allow-test-domains

```

This mode allows `api.github.com` and `httpbingo.org` in addition to Azure domains. The test suite in [`src/ssrf.rs`](https://github.com/microsoft/pg_durable/blob/main/src/ssrf.rs) covers edge cases including bare IPs, query/fragment bypasses, and Unicode homographs.

## Summary

- **`df.http()` is opt-in** by default because `EXECUTE` is revoked from `PUBLIC` in [`src/lib.rs`](https://github.com/microsoft/pg_durable/blob/main/src/lib.rs) during installation.
- The **`http-allow-azure-domains` Cargo feature** restricts outbound traffic to approved Azure data-plane domains and implies SSRF protections.
- **SSRF defenses** include a private IP blocklist (`check_blocked_ip`), hostname allow-list validation (`validate_url_allowlist`), and DNS-rebinding guards (`SsrfSafeResolver`).
- **Runtime enforcement** via `has_function_privilege` in [`src/activities/execute_http.rs`](https://github.com/microsoft/pg_durable/blob/main/src/activities/execute_http.rs) ensures only explicitly authorized roles can trigger HTTP requests.
- **Production builds** should always use `http-allow-azure-domains`; `http-allow-all` is strictly for development.

## Frequently Asked Questions

### What happens if I compile pg_durable without any HTTP feature flags?

All outbound HTTP is blocked. `df.http()` will fail at both DSL-time and execution-time because the SSRF allow-list code in [`src/ssrf.rs`](https://github.com/microsoft/pg_durable/blob/main/src/ssrf.rs) requires an explicit feature to permit any domain.

### Can a malicious workflow bypass the Azure domain allow-list using redirects?

No. The `http-allow-azure-domains` feature explicitly **blocks redirects**. Even if an initial request targets an allowed Azure domain, following a redirect to a non-allowed endpoint is prevented by the SSRF logic in [`src/ssrf.rs`](https://github.com/microsoft/pg_durable/blob/main/src/ssrf.rs).

### How do I audit which roles can execute `df.http()`?

Query the PostgreSQL system catalogs to inspect function privileges, or review the audit checklist in [`SECURITY.md`](https://github.com/microsoft/pg_durable/blob/main/SECURITY.md). Because privileges are revoked from `PUBLIC` by default, any role with access must have been explicitly granted `EXECUTE`, making the audit surface small and deterministic.

### Is `http-allow-all` safe for local development?

It is **intended only for development**. This feature disables the IP blocklist, bare-IP rejection, and domain allow-list, removing all SSRF protections. It should never be enabled in production builds or container images.