{
  "schema": "aaes/capability-pack/1",
  "provider": "airflow",
  "version": "1.0.0",
  "description": "Apache Airflow 2 stable REST API v1: read workflows (DAGs), runs, and connections; pause or unpause workflows; create or delete connections; trigger runs; and delete workflow metadata. Runs can change downstream systems. Airflow 3 API v2 is excluded.",
  "notes": "The endpoint host 'airflow-instance.invalid' is a reserved non-production placeholder (an RFC 2606 reserved TLD): substitute the customer's own Airflow webserver host before enabling, in every endpoint, in the resource locator and in every egress allowlist entry — the allowlist matches exactly, so all three must be substituted together, and substitution is a required deployment step. DAG_ID, DAG_RUN_ID and CONNECTION_ID in the paths are placeholders the deployment binds to concrete values before enabling.\n\nThis pack registers the stable REST API v1 (server path /api/v1) as specified by the Airflow 2.10.5 OpenAPI definition; Airflow 3 ships a different public API (/api/v2, JWT via /auth/token under the configured auth manager) and this pack registers none of it. The rendered stable-rest-api-ref.html documentation page is a JavaScript application that yielded no operation content to the retrieval tool used for this review, so the review was made against the raw OpenAPI source that page renders, plus the API security pages for both major lines; that is an honest limitation of the retrieval, not a gap in the underlying specification.\n\nExcluded operations: the collection-wide PATCH /api/v1/dags (which updates every DAG matching dag_id_pattern, including '~' for all DAGs — a bulk pause/unpause of the whole estate in one call); POST /api/v1/dags/{dag_id}/clearTaskInstances and POST /api/v1/dags/{dag_id}/updateTaskInstancesState (bulk task-instance state changes that re-run or rewrite execution state across a date range); DELETE /api/v1/dags/{dag_id}/dagRuns/{dag_run_id} (DAG-run deletion; single-run history deletion is a narrower review the deployment can add); the /variables endpoints (Airflow Variables commonly hold secrets and are readable by every task); the /users, /roles and /permissions endpoints (identity and access management for the deployment itself); the /pools write operations; /config and /plugins (deployment internals); and POST /api/v1/connections/test, which the specification documents as disabled by default and strongly advised to stay disabled except for highly trusted users.\n\nApproval classification: triggering a DAG run is registered as deploy at the minimum R3 tier, not as an ordinary create — a DAG run executes the DAG's tasks, which can write to any system the DAG touches, so the effect is deploy-equivalent; deploy is one of the designated actions requiring human approval. DAG deletion is registered as delete at the minimum R3 tier: the OpenAPI description states it deletes all metadata related to the DAG including finished DAG runs and tasks, and that the action cannot be undone.\n\nThe POST /api/v1/dags/~/dagRuns/list capability is a collection read that uses POST only because a GET with many DAG ids would exceed URL length limits; its body is a query form (ListDagRunsForm: dag_ids, states, execution-date ranges, page limit and offset), it changes nothing, and it is registered read at the minimum R0 tier.",
  "credential": {
    "secret_ref_id": "secret-ref-airflow-basic-auth",
    "custody": "customer_vault",
    "inject": "header",
    "inject_name": "Authorization",
    "note": "Credentials for the customer's own Airflow webserver, held in the client's own secret manager. The stored material must be the complete Authorization header value (scheme included) — for the basic-auth backend, 'Basic ' followed by the base64 of 'username:password'. Token issuance: the stable API v1 has no first-class scoped-token issuance; authentication is per-request against the webserver's configured auth backend. Version-corrected default: the pinned 2.10.5 configuration contract is [api] auth_backends, whose documented DEFAULT is the session backend ([api] auth_backends = airflow.api.auth.backend.session); on FAB-auth-manager deployments the equivalent setting is [fab] auth_backends = airflow.providers.fab.auth_manager.api.auth.backend.session.\n\nEither way, basic authentication is not on by default and a deployment must explicitly configure the basic_auth backend (airflow.api.auth.backend.basic_auth, or airflow.providers.fab.auth_manager.api.auth.backend.basic_auth under the FAB auth manager) for the credential stored here to work at all. The credential is therefore a long-lived webserver user password, not an expiring token: it does not expire on its own, it stops working only when the password is rotated or the user is disabled (each request is authenticated, so disablement takes effect immediately — there is no already-issued token that outlives revocation, and no refresh for the executor to perform; any renewal is an external deployment process that updates the stored value).\n\nLeast privilege: create a dedicated API user with the minimum webserver RBAC roles that make these capabilities work (read-only roles for the reads; operational roles for trigger and connection writes) and nothing more — an admin user would also carry the unregistered /users, /roles and /config surfaces.\n\nExecutor hygiene: the value must live only in executor memory for the lifetime of a call, must never be written to logs, traces or diagnostics, and must not appear in crash dumps; a deployment that cannot meet that bar must not enable the pack. Deployments on Airflow 3 should not reuse this reference: its auth manager issues short-lived JWTs from /auth/token against /api/v2, which is a different credential lifecycle and a different registration."
  },
  "capabilities": [
    {
      "capability_id": "airflow.dags.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "airflow-instance.invalid"
      ],
      "endpoint": "https://airflow-instance.invalid/api/v1/dags",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airflow-basic-auth",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "airflow-instance",
      "description": "List the DAGs in the deployment's metadata database; this pack registers the collection read only — single-DAG reads (GET /api/v1/dags/{dag_id}) and GET /api/v1/dags/{dag_id}/details are not registered. DAG names, tags and schedules reveal the organization's pipeline layout.\n\nThe host is a reserved placeholder: substitute the customer's webserver host before enabling.",
      "requires_review": true
    },
    {
      "capability_id": "airflow.dag_runs.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "airflow-instance.invalid"
      ],
      "endpoint": "https://airflow-instance.invalid/api/v1/dags/DAG_ID/dagRuns",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airflow-basic-auth",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "airflow-instance",
      "description": "List DAG runs for one DAG; this pack registers the collection read only — single-run reads (GET /api/v1/dags/{dag_id}/dagRuns/{dag_run_id}) are not registered. The API accepts '~' as the dag_id to list runs across all DAGs; whether the deployment permits that pattern is a binding decision, not a restriction this registration imposes. DAG_ID is a placeholder the deployment binds before enabling.",
      "requires_review": true
    },
    {
      "capability_id": "airflow.dag_runs.list_batch",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "airflow-instance.invalid"
      ],
      "endpoint": "https://airflow-instance.invalid/api/v1/dags/~/dagRuns/list",
      "method": "POST",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airflow-basic-auth",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "airflow-instance",
      "description": "Batch-list DAG runs across many DAGs. This is a POST-with-query-body endpoint used read-only: the specification defines it as a POST only because a GET filtering across a large number of DAG ids would exceed HTTP request URL length limits, and the request body is a query form (ListDagRunsForm: dag_ids, states, execution-date range filters, page limit and offset). It changes nothing and is registered read at the minimum R0 tier.\n\nThe host is a reserved placeholder: substitute the customer's webserver host before enabling.",
      "requires_review": true
    },
    {
      "capability_id": "airflow.connections.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "airflow-instance.invalid"
      ],
      "endpoint": "https://airflow-instance.invalid/api/v1/connections",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airflow-basic-auth",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "airflow-instance",
      "description": "List the stored Airflow connections; this pack registers the collection read only — single-connection reads (GET /api/v1/connections/{connection_id}) are not registered. Connection records describe the infrastructure the deployment reaches (types, hosts, ports, logins and extras), which is sensitive internal configuration even where secret fields are not rendered; treat the response accordingly.\n\nThe host is a reserved placeholder: substitute the customer's webserver host before enabling.",
      "requires_review": true
    },
    {
      "capability_id": "airflow.dag.update",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R3",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "airflow-instance.invalid"
      ],
      "endpoint": "https://airflow-instance.invalid/api/v1/dags/DAG_ID",
      "method": "PATCH",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airflow-basic-auth",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "update",
        "deploy"
      ],
      "resource_id": "airflow-instance",
      "description": "Update one DAG record — the operational use is pausing and unpausing via is_paused with the update_mask query parameter. Unpausing is not an ordinary reversible update: it releases the DAG's queued and scheduled execution, and the tasks then run against whatever systems the DAG is wired to, so the effect is deploy-equivalent and the capability declares update and deploy at the minimum R3 tier. This registration does not restrict which fields the request body may carry: an installer's intention of pause-only is not a restriction, and a reviewer who wants that must impose it in policy.\n\nThe collection-wide PATCH /api/v1/dags (dag_id_pattern, including '~' for all DAGs) is a different, bulk operation and is deliberately not registered by this pack. DAG_ID is a placeholder the deployment binds before enabling.\n\nOn supported AAES-mediated execution paths, the declared irreversible actions require an authorized person’s approval. Observation-only and bypass calls cannot be stopped by this control.",
      "requires_review": true
    },
    {
      "capability_id": "airflow.connection.create",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R2",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "airflow-instance.invalid"
      ],
      "endpoint": "https://airflow-instance.invalid/api/v1/connections",
      "method": "POST",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airflow-basic-auth",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "create"
      ],
      "resource_id": "airflow-instance",
      "description": "Create one stored connection in the Airflow metadata database.\n\nThe request body is a Connection record and can carry credential material (login, password, extra) that Airflow then stores and serves to tasks — the body of a call to this capability is itself sensitive: the executor must keep the request body out of logs, traces, diagnostics and crash dumps exactly like the injected Authorization material, must not buffer it beyond the lifetime of the call, and any deployment-side request recording must redact it.\n\nCreation is reversible by deletion, but any secret written is thereafter held in Airflow's own store, outside this capability's reach; review what the integration is permitted to write before enabling.\n\nThe host is a reserved placeholder: substitute the customer's webserver host before enabling.",
      "requires_review": true
    },
    {
      "capability_id": "airflow.dag_run.trigger",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R3",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "airflow-instance.invalid"
      ],
      "endpoint": "https://airflow-instance.invalid/api/v1/dags/DAG_ID/dagRuns",
      "method": "POST",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airflow-basic-auth",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "deploy"
      ],
      "resource_id": "airflow-instance",
      "description": "Trigger a new DAG run. This is registered with the deploy action at the minimum R3 tier rather than as an ordinary create: a DAG run executes the DAG's tasks, which can write to any system the DAG is wired to — production databases, warehouses, third-party APIs — so the effect is deploy-equivalent, and the request body's conf object is an unrestricted parameter payload handed to that execution.\n\nDeploy is one of the designated actions requiring human approval.\n\nOn supported AAES-mediated execution paths, irreversible actions require approval by an authorized person; this does not prevent calls through bypass or observation-only paths. If the DAG is paused the run remains queued per the API's documented behavior, which changes timing, not effect. DAG_ID is a placeholder the deployment binds before enabling.",
      "requires_review": true
    },
    {
      "capability_id": "airflow.dag.delete",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R3",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "airflow-instance.invalid"
      ],
      "endpoint": "https://airflow-instance.invalid/api/v1/dags/DAG_ID",
      "method": "DELETE",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airflow-basic-auth",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "delete"
      ],
      "resource_id": "airflow-instance",
      "description": "Delete a DAG's metadata. The OpenAPI description is explicit: this deletes all metadata related to the DAG, including finished DAG runs and tasks, logs are not deleted, and the action cannot be undone — the run history is the audit trail for past executions, and re-adding the DAG code later does not restore it. Registered as delete at the minimum R3 tier; delete is one of the designated actions requiring human approval. DAG_ID is a placeholder the deployment binds before enabling.\n\nOn supported AAES-mediated execution paths, the declared irreversible actions require an authorized person’s approval. Observation-only and bypass calls cannot be stopped by this control.",
      "requires_review": true
    },
    {
      "capability_id": "airflow.connection.delete",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R3",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "airflow-instance.invalid"
      ],
      "endpoint": "https://airflow-instance.invalid/api/v1/connections/CONNECTION_ID",
      "method": "DELETE",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airflow-basic-auth",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "delete"
      ],
      "resource_id": "airflow-instance",
      "description": "Delete a stored connection, including any credential material held in it. Recreating the connection later is a new write, not an undo: unless the caller preserved the previous secret values elsewhere they are gone, and every DAG that references the connection id starts failing at runtime — the blast radius extends beyond the deleted record. Registered as delete at the minimum R3 tier; delete is one of the designated actions requiring human approval. CONNECTION_ID is a placeholder the deployment binds before enabling.\n\nOn supported AAES-mediated execution paths, the declared irreversible actions require an authorized person’s approval. Observation-only and bypass calls cannot be stopped by this control.",
      "requires_review": true
    }
  ],
  "resources": [
    {
      "resource_id": "airflow-instance",
      "kind": "api",
      "locator": "https://airflow-instance.invalid/api/v1",
      "data_class": "internal",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    }
  ],
  "review": {
    "state": "library",
    "date": "2026-09-22",
    "sources": [
      "https://raw.githubusercontent.com/apache/airflow/2.10.5/airflow/api_connexion/openapi/v1.yaml (retrieved 2026-09-21)",
      "https://airflow.apache.org/docs/apache-airflow/2.10.5/security/api.html (retrieved 2026-09-21)",
      "https://airflow.apache.org/docs/apache-airflow/stable/security/api.html (retrieved 2026-09-21)",
      "https://airflow.apache.org/docs/apache-airflow-providers-fab/stable/auth-manager/api-authentication.html (retrieved 2026-09-21)"
    ],
    "reviewer_id": "aaes-eng"
  }
}
