Default-Deny Unaudited Airtable Tools
Maintains a per-tenant allowlist of audited Airtable tool-name suffixes and denies any call whose tool name does not end with an allowlisted entry.
- Direction
- ingress
- Rego package
airtable.ingress.default_deny_unknown_tools- App
- airtable
- Bundle
- soc2
- Published
- Minimum gateway
- 1.0.0b24
- Schema version
- 1.0.0
- Checksum
sha256:3ffaade35ac5b90afb59c43af5f65415acdf63f1d13f6ec07f7acbcad093c930
airtabledefault-deny-unknown-toolsallowlistingresssoc2
What this policy does
Direction: ingress (tool_pre_invoke)
Default: deny — only allowlisted tool-name suffixes pass
Package: airtable.ingress.default_deny_unknown_tools
What it does
Maintains a per-tenant allowlist of audited Airtable tool-name suffixes and denies any call whose tool name does not end with an allowlisted entry. Everything not explicitly reviewed is blocked before it reaches the Airtable MCP server, and the deny is surfaced as a gateway event — the drift signal that flags a new, renamed, or newly enabled tool the moment it first appears.
Airtable is a case where default-deny is mandatory, not optional hardening, for two concrete reasons:
- An unverified, oversized community surface. The
rashidazarang/airtable-mcpcommunity server advertises ~42 tools "covering every Airtable PAT scope" — full CRUD, batch operations, schema management, and webhook management. Its individual tool names are not verified from source, so a per-tool blocklist against it is impossible to keep complete. Its webhook tools are the standout risk: a webhook creates a persistent outbound data channel that survives the MCP session, exfil that outlives the agent turn. Under default-deny, none of these tools is reachable until an operator has introspected the server live and pinned the exact names. - Upstream servers grow over time. The official Airtable server added
upload_attachmentin May 2026; community servers add tools on their own cadence. A blocklist silently admits every future addition. Default-deny fails those closed until they are audited and added to the allowlist.
It also catches upstream renames: a tool that stops matching an allowlisted suffix is denied until it is re-audited. This is the default-deny-by-design posture — the default state of any tool the tenant has not reviewed is "inaccessible."
A missing, empty, non-string, or non-ASCII tool name matches nothing and is denied (fail closed).
Pin the allowlist to YOUR tenant at import time
The shipped allowed_tool_suffixes array is a starter set built from the
verified official-server tools (per the
Airtable support doc)
and the verified domdomegg/airtable-mcp-server terse equivalents (per its
GitHub README). It is not,
and cannot be, the list of tools your deployment has reviewed — in
particular it contains nothing from the unverified rashidazarang surface.
At import time, replace or extend the array with exactly the suffixes your
team has audited after live introspection.
Because the DTwo gateway prepends the configured server name to each tool, the exact string the gateway sends is deployment-specific. Verify the precise suffixes with the dump-input debug technique before pinning — do not guess. Add only what you have reviewed; every unlisted tool is denied until you do.
Compliance alignment
- SOC 2 CC6.1 — supports logical access security over protected assets: the agent channel can only reach Airtable capabilities that were explicitly reviewed and enumerated, not the whole surface an OAuth grant / PAT exposes.
- SOC 2 CC6.6 — supports boundary protection against external threats: upstream-added, renamed, or unverified community tools (including the rashidazarang webhook-persistence tools) do not become reachable through the gateway boundary without an explicit allowlist change.
- SOC 2 CC6.8 — supports prevention of unauthorized software: any tool the tenant has not audited — including a self-expanding or dynamically enabled server's additions — is unauthorized-by-default on the agent path.
- SOC 2 CC7.2 / CC7.3 — deny decisions from this policy surface tool drift (new/renamed upstream tools, newly enabled community surfaces) as observable gateway events that feed anomaly monitoring and event evaluation.
- GDPR Art. 25 — supports data protection by design and by default on the agent channel: the default state of any new data-bearing Airtable tool is "inaccessible until audited," and Airtable bases routinely hold personal data (CRM contacts, applicant-tracking pipelines, and — on HIPAA-eligible Enterprise plans — health-ops rows).
Tool name matching
The official server uses verbose suffixed names (list_records_for_table,
create_records_for_table, get_record_for_page); the community
domdomegg server uses terse ones (list_records, create_record,
get_record). Behind the DTwo gateway both appear as
<configured-server-name>-<tool-name>, and that prefix is not standardized
across deployments.
To cover the gateway prefix with a single allowlist entry, the policy matches
case-insensitively on lower(input.resource.name) with endswith against
each audited name:
list_records_for_table(official) — matches,list_records(domdomegg terse) — a separate entry, becauselist_records_for_tabledoes not end withlist_records,airtable-list_records_for_table(gateway-prefixed) — ends withlist_records_for_table, matches.
Because the official and terse spellings are not suffixes of one another, both are enumerated explicitly rather than collapsed into a shorter stem — this is deliberate, so that suffix matching stays specific enough to preserve drift detection (see Known limitations).
Before matching, the raw (pre-lowercase) name must consist only of the
ASCII set real tool names and gateway prefixes use — [A-Za-z0-9._-]. This is
checked before lower() runs, which closes a Unicode case-folding evasion:
lower() folds a handful of non-ASCII code points onto ASCII letters (e.g.
the Kelvin sign U+212A → k), so an allowlisted suffix could otherwise be
spoofed with a folded homoglyph. A name containing any character outside that
ASCII set is denied.
Allowlisted tools
The starter allowlist covers the verified official-server tools —
ping, list_bases, search_bases, list_workspaces,
list_tables_for_base, get_table_schema, list_records_for_table,
search_records, list_records_for_page, get_record_for_page,
create_records_for_table, update_records_for_table, upload_attachment —
and the verified domdomegg terse equivalents — list_tables,
describe_table, list_records, get_record, create_record,
update_records, create_table, update_table, create_field,
update_field, list_comments, create_comment.
Everything else is deliberately excluded and must be audited before it is added, including:
- The community destructive path —
delete_records(domdomegg batch delete by ID array — the biggest capability delta vs. the official server) anddelete_page. - The interactive widget —
display_records_for_table(disabled by default on the official server). - Structure / external-exposure tools —
create_base,create_interface,create_page,publish_interface,list_pages_for_base,describe_page_element,describe_page_type. - Every unverified rashidazarang tool, including its webhook-management
tools — none is on the list, so it is denied by default unless its name
happens to end with an allowlisted suffix (see the short-suffix
pingresidual under Known limitations, which is the one way an un-audited community tool can slip through this name-only gate).
Argument shape
This policy inspects only the tool name (input.resource.name); it reads
no arguments, so it is insensitive to argument-shape differences between the
official and community servers. A missing resource or resource.name
resolves to "" via object.get and matches nothing (deny). A non-string
name (null, number, object, array — a malformed or hostile request) is coerced
to "" rather than passed to lower(); without that guard lower() would
raise a built-in type error that leaves allow and reason undefined — a deny
with no surfaced reason. With the guard it is a clean, reasoned deny.
Examples
Allowed
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "airtable-list_records_for_table", "type": "tool" },
"payload": {
"name": "airtable-list_records_for_table",
"args": { "baseId": "appABC", "tableId": "tbl123" }
}
}
}
allow = true, no reason.
Denied
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "airtable-delete_records", "type": "tool" },
"payload": {
"name": "airtable-delete_records",
"args": { "baseId": "appABC", "tableId": "tbl123", "recordIds": ["rec1"] }
}
}
}
allow = false, reason = "This Airtable tool is not on the audited allowlist (...)".
(delete_records ends in e_records, which does not match the allowlisted
update_records / search_records / list_records suffixes — so the batch
delete tool stays denied.)
Composition
This policy is the outer gate — it decides which Airtable tools exist for agents. Pair it with policies that constrain how the allowlisted tools are used:
apps/airtable/fence-base-allowlist— confine the allowlisted record/schema tools to a set of sanctionedapp…base IDs.apps/airtable/redact-pii-egress— mask PII inlist_records*/search_records/get_record*responses.- A bulk-read clamp companion (
cap-bulk-record-reads) — clampmaxRecordsand govern the rawfilterByFormulaquery surface onlist_records*, which is Airtable's only raw-query risk (see Known limitations).
Known limitations
The starter allowlist is not your tool list. Pinning it to the suffixes your tenant has actually reviewed is a required deployment step, not a tuning step. If you swap in a community server (including rashidazarang), every tool it adds is denied until you introspect and add it.
rashidazarang tool names are unverified. The ~42 tools that server advertises were not verified from source in the app research, so none is on the allowlist and this policy makes no claim about their exact spellings. Before allowlisting anything from that server you must introspect it live (dump-input) and confirm each name — in particular, do not allowlist its webhook tools without understanding that a webhook opens a persistent outbound channel that outlives the MCP session.
PF-07 warehouse-SQL guards do not apply. Airtable exposes no SQL/DAX surface, so the
guard-warehouse-sqlfamily is out of scope here. The residual raw-query risk is thefilterByFormulastring onlist_records*, which this name-only gate does not inspect — handle it with thecap-bulk-record-readscompanion.endswithmatching trusts the suffix, not a separator. To absorb any gateway server-name prefix with one entry, matching does not require a separator before the suffix. As a result a tool literally named<anything>get_record(any prefix glued directly onto an allowlisted suffix) would also match. No tool in the current verified Airtable inventory collides this way — notablydelete_records,display_records_for_table,list_pages_for_base, andcreate_basedo not end with any allowlisted suffix and are denied.The short
pingsuffix is the sharp edge of that residual.pingis only four characters and is a common English word-ending, so any un-audited tool whose name happens to end inpingis silently allowed — e.g. a community tool namedbulk_dumping,record_scraping,field_stripping, ormappingall match thepingsuffix and pass the gate. This is the most realistic way the unverified rashidazarang surface can reach an agent by accidental collision despite the "default-deny" posture, so the "all unaudited tools are denied" statements above carry this caveat. This directly undercuts the headline webhook claim. The policy deniescreate_webhook(see Examples/tests), but the ping residual means a webhook tool whose name simply ends inpingis allowed — and "ping" is a standard webhook concept (most webhook APIs expose aping/test event), so a plausibly-namedwebhook_ping(or..._ping) tool on the very persistent-channel surface this policy is built to fence would slip through by accidental collision. The "webhook tools stay closed until audited" statement therefore carries the same ping caveat as everything else. The long suffixes (get_record,list_records, …) are much less exposed to accidental collision because an English word rarely ends in_record/_records.They are not safe against a deliberately or adversarially chosen name, though: because the match is
endswith, a crafted tool name (from a compromised or self-expanding community server) can glue any prefix onto any allowlisted suffix —evil_upload_attachmentends with the long write suffixupload_attachmentand is allowed, just asbulk_dumpingends withping. Lengthening the allowlist entries does not close this: since the operator is stillendswith, replacingpingwith the fullerairtable-pingdoes not stopx-airtable-ping(or any other glued prefix) from matching — it only makes an accidental collision less likely. To truly eliminate the glued-prefix residual you must change the match fromendswithto exact equality against the full gateway tool name (e.g.lower(input.resource.name) == "airtable-ping"), which no prefix can satisfy. Prefer exact-equality matching if you front an untrusted server.Name-based trust only. The policy audits tool names, not behavior. A tool that keeps an allowlisted name but changes behavior upstream bypasses the intent while matching the letter. Re-audit when upstream servers change.
ASCII-only tool names. Matching requires the raw name to be
[A-Za-z0-9._-]. This is deliberate (it blocks Unicode case-fold and homoglyph spoofing), but a deployment whose configured MCP server name contains other characters (spaces,@,/, non-ASCII) would see even its legitimate tools denied; rename the server to an ASCII slug, or relax the character class, if so.Exact upstream suffixes unverified for your gateway. The official and domdomegg names are verified from their docs, but the string your gateway actually sends depends on the configured server name. Confirm with dump-input before pinning.
No identity-based exemptions. All callers face the same allowlist. If you need a platform-admin break-glass group that can call unaudited tools, add a separate
allow ifbranch gated oninput.subject.claimsgroups. (Group names would be placeholders — replace them with your IdP's group name at import time.)
Compliance note. This policy supports alignment with the cited framework controls on the MCP path only. No policy or bundle makes an organization compliant with any framework; web-UI, native-API, and in-app access are outside the gateway's reach by design. Validate against your own compliance program before relying on it.
Policy source (Rego)
package airtable.ingress.default_deny_unknown_tools
# Deny-by-default: a tool call is allowed only if its name ends with an audited
# allowlist suffix below. A missing, empty, non-string, or non-ASCII tool name
# matches nothing and is therefore denied (fail closed).
default allow := false
# Audited Airtable tool-name suffixes — STARTER SET. Pin to the suffixes YOUR
# tenant has actually reviewed at import time (see the policy description).
# Matched with endswith so one entry also covers any gateway server-name prefix
# (airtable-list_records_for_table). The official verbose spelling and the
# domdomegg terse spelling are NOT suffixes of one another, so both are listed
# explicitly — this keeps each suffix long/specific enough to preserve drift
# detection.
#
# Sources:
# - Official server: support.airtable.com "Using the Airtable MCP server"
# - domdomegg/airtable-mcp-server (GitHub README)
#
# Deliberately EXCLUDED — default-deny by design, audit before adding any:
# - Community destructive: delete_records (batch delete by ID), delete_page
# - Interactive widget: display_records_for_table (off by default)
# - Structure / exposure: create_base, create_interface, create_page,
# publish_interface, list_pages_for_base,
# describe_page_element, describe_page_type
# - EVERY unverified rashidazarang tool, incl. webhook-management tools that
# open a persistent outbound channel surviving the MCP session.
allowed_tool_suffixes := [
# --- Verified official server tools ---
"ping",
"list_bases",
"search_bases",
"list_workspaces",
"list_tables_for_base",
"get_table_schema",
"list_records_for_table",
"search_records",
"list_records_for_page",
"get_record_for_page",
"create_records_for_table",
"update_records_for_table",
"upload_attachment",
# --- Verified domdomegg terse equivalents ---
"list_tables",
"describe_table",
"list_records",
"get_record",
"create_record",
"update_records",
"create_table",
"update_table",
"create_field",
"update_field",
"list_comments",
"create_comment",
]
# Raw tool name straight from the request. Missing resource/name resolves to ""
# via object.get and matches nothing (fail closed).
raw_tool_name := object.get(object.get(input, "resource", {}), "name", "")
# Tool name, lowercased. A non-string name (null, number, object, array — a
# malformed or hostile request) is coerced to "" instead of being handed to
# lower(), which would raise a built-in type error and leave allow/reason
# undefined. Coercing keeps the decision a clean, reasoned deny (fail closed).
tool_name := lower(raw_tool_name) if is_string(raw_tool_name)
tool_name := "" if not is_string(raw_tool_name)
# Character-class guard on the RAW (pre-lowercase) name. Real Airtable / community
# tool names and gateway <server-name>- prefixes use only ASCII letters, digits,
# underscore, dot, and hyphen. Checking the raw name BEFORE lower() closes a
# Unicode case-folding evasion: lower() folds some non-ASCII code points onto
# ASCII letters (e.g. the Kelvin sign U+212A -> "k"), so an allowlisted suffix
# could be spoofed with a folded homoglyph and slip past the default-deny gate
# despite being a visibly different, un-audited name. The regex is NOT multiline
# in OPA/Go, so a spliced newline breaks the whole-string match. Guarded by
# is_string so a non-string name still yields a clean, reasoned deny.
raw_name_is_plain_ascii if {
is_string(raw_tool_name)
regex.match(`^[A-Za-z0-9._-]+$`, raw_tool_name)
}
# Allow only when the name ends with an audited suffix. endswith (no separator
# requirement) is intentional: it matches the bare name (list_records_for_table)
# and any gateway server-name prefix (airtable-list_records_for_table) with a
# single allowlist entry. See Known limitations for the residual this trades for.
allow if {
raw_name_is_plain_ascii
some suffix in allowed_tool_suffixes
endswith(tool_name, suffix)
}
reason := "This Airtable tool is not on the audited allowlist, so the gateway denies it by default and surfaces the call as tool drift. Default-deny is mandatory for Airtable because the rashidazarang community server advertises ~42 unverified tools — including webhook-management tools that open a persistent outbound data channel surviving the MCP session — and because upstream servers add tools over time (e.g. the May-2026 upload_attachment), so a new or renamed tool must fail closed until it is reviewed. Ask a gateway admin to introspect the tool live and, if it is safe for agents, add its verified name suffix to the per-tenant allowlist." if not allow Canonical source: policy.md on GitHub · raw · raw on this site (.md)
Related policies
Airtable: Redact PII in Record Reads
Scans the responses of the Airtable record-read tools — the calls that return row fields values — and rewrites high-confidence PII shapes to a fixed…
Asana: Redact PII in Task & Comment Reads
On the Asana MCP read path, this transform scans the free-text business fields that ride back in task, comment/story, and status-update responses — notes,…
BigQuery: Redact PII in Query Results
Scans the content returned by BigQuery's result-returning tools and rewrites high-confidence PII shapes to fixed, non-recoverable redaction tokens before the…
Block Agent Email to External Recipients
Blocks agent-initiated Microsoft 365 email sends when any recipient address falls outside a corporate-domain allowlist.
Block BigQuery Exfiltration and Cross-Project Writes
Inspects the raw GoogleSQL string carried by BigQuery SQL tools and denies any statement that moves data out of the tenant's own project — even when the call…
bigqueryguard-warehouse-exportingresssqlexfiltrationsoc2pci-dssgdpr-ccpa
Block Bulk Export & External Staging (Snowflake)
Blocks Snowflake SQL-execution tool calls whose query text moves whole tables off the Snowflake perimeter — bulk export to cloud storage or a stage, and…
snowflakeguard-warehouse-sqlexportexfiltrationingresssoc2pci-dssgdpr-ccpa