Corsair vs n8n: Keeping Sensitive Data Out of Integration Logs and Execution History
Every integration call leaves data behind in logs, audit tables and execution history. See how Corsair and n8n compare on redaction, retention, access control and debugging without raw customer data.

TL;DR
- Corsair stores integration records (synced entities, an event audit log and approval requests) in your own database, so you decide what stays and for how long.
- Credentials are encrypted by default. Payload redaction and retention are rules you write with hooks, error handlers and database jobs.
- n8n takes a platform route: execution history lives inside the platform, with redaction, pruning and role settings built in.
- Troubleshooting works without raw customer data when you keep IDs, statuses, endpoint names and error categories.
- Test any setup by planting a fake customer value and searching every table, log and backup for it.
Every integration call leaves something behind: an argument here, a response there, an error string, a row in a table. Those leftovers are where customer data tends to surface long after the call has finished, in logs, audit tables and execution history that more people can read than the original request ever reached.
This post follows customer data through an integration layer and compares how Corsair and n8n handle it. The focus is operational: where sensitive data in integration logs and records appears, how to redact it, how long to keep it, who can read it, and what engineers still need to debug a failed run. The short version: n8n handles much of this through platform settings, while Corsair, an MCP integration layer that runs inside your app, keeps every record in your own database and leaves redaction and retention rules in your hands. Both are covered fairly, including the places where Corsair's docs stay silent.
Where Does Customer Data Land When an Agent Calls an Integration, and Who Owns That Record?
With Corsair, customer data lands in tables inside your own database, so you own every integration record. When an agent calls an operation, the arguments pass through your app, the provider's response is upserted into a synced copy, and the call is logged in an audit table. Corsair uses the same five tables for every integration, however many you add.
n8n works differently. Data moves through node input and output payloads and is saved as execution history inside the platform, and that history is the record you open when something fails.
Here is where customer information can sit in a Corsair setup:
- Request arguments: the values an agent passes to an operation are visible to your own hooks. If the call needs approval, the arguments are frozen as JSON in
corsair_permissions. - Tool responses: the provider's response is stored in
corsair_entities, a synced copy of resources such as messages, issues or repositories. Later API calls and webhooks update the same row in place instead of adding a new copy. - Webhook payloads: incoming events go through one handler and update the same entity rows.
- The audit trail:
corsair_eventsis an audit log of API calls and webhooks, which makes it Corsair's AI agent audit log. Itspayloadcolumn holds JSON, and the docs don't spell out the contents for every event type, so inspect a few real rows in staging before you decide what it holds. - Errors: typed errors and error handlers run in your process, so error text lands wherever you choose to log it.
- Hub: if you use Corsair Hub for connect pages and approvals, it stores none of your tenants' tokens and never touches your database. During an approval it does keep a short session holding the plugin, endpoint, call arguments and tenant ID until the request is decided or expires.
One scope note: this post covers Corsair running inside your own app with your own database. Corsair Cloud, the hosted runtime, is a different deployment shape.
How Do You Keep Sensitive Fields Out of Stored Records?
With Corsair, you keep sensitive fields out through rules you write in hooks and error handlers, with your database policies underneath. Credentials are protected by default through envelope encryption, but the docs describe no built in switch that redacts customer fields from stored records. To redact PII from logs and tables, you design the rule yourself.
n8n ships a feature for this. Execution data redaction, an Enterprise feature, replaces each node's input and output data with a redacted marker in the execution viewer, while status, timing and node names stay visible. It also reduces error messages to the error type and HTTP status. The documented limits matter: n8n applies redaction when data is served through its API and doesn't change what is stored in the database, and it doesn't cover Code node console output, webhook responses or data flowing between nodes. Before this feature existed, the documented alternative was switching off execution history for a workflow, which removed failure visibility along with the data.
Where Corsair gives you control:
- Credentials: one master key (the KEK) sits in your environment and each connection gets its own data key, so tokens are encrypted in your database and one compromised connection key doesn't expose the others.
- Hooks: before and after hooks run around every call. Before hooks can validate or change arguments. After hooks run once the operation completes, so they are the place to decide what gets logged or forwarded.
- Webhook hooks: a before hook can modify the payload before processing, and what you return is what the handler receives.
- Error handlers: error handlers match errors and decide retries. Log the error class and the operation name rather than the full message, because provider errors can echo the value they rejected.
An API before hook is different from a webhook before hook: it changes the request that goes to the provider, so don't strip fields the provider needs. Use it for validation and defaults, and put redaction on what you log and what you keep.
Here is a sketch of a webhook hook that scrubs known sensitive keys before processing. Adjust the field names to your plugin's payload, and check the return shape against the Triggers docs for your version.
const SENSITIVE_KEYS = new Set(["email", "phone", "address"]);
function scrub(value: unknown): unknown {
if (Array.isArray(value)) return value.map(scrub);
if (value && typeof value === "object") {
return Object.fromEntries(
Object.entries(value).map(([key, v]) =>
SENSITIVE_KEYS.has(key.toLowerCase())
? [key, "[redacted]"]
: [key, scrub(v)]
)
);
}
return value;
}
webhookHooks: {
messages: {
message: {
before: async (ctx, request) => {
return { ctx, args: scrub(request) };
},
},
},
},
One caveat from the webhook docs: when an update arrives for a record Corsair hasn't seen, it fetches the latest data from the provider's API and creates the row. Scrubbing a webhook payload doesn't change what a backfilled row contains, so test the stored result instead of assuming.
How Long Should Integration Records Live, and Who Enforces It?
Keep each kind of record only as long as its purpose needs it. With Corsair, you enforce that at the database layer, because the tables are yours and the docs describe no built in retention setting. In n8n, execution history retention is a platform setting handled through save options and automatic pruning.
On the n8n side:
- Save settings: by default n8n saves execution data for successful, failed and manual runs. Each can be switched off per workflow or for the whole instance.
- Pruning: it is on by default. Finished executions are deleted after 336 hours (14 days) or when the count passes 10,000, whichever comes first, and both limits are configurable.
- Exceptions: annotated executions, such as ones with tags or ratings, aren't pruned, and binary data pruning follows the active storage mode.
On the Corsair side, retention depends on the table:
corsair_entities: one row per record, updated in place, so volume follows the number of records rather than the number of calls. Retention here means deleting a tenant's rows when they disconnect or leave, not trimming history.corsair_events: an audit log, so it grows with traffic. This is the table that needs a schedule.corsair_permissions: approval requests stay as rows after they complete, are denied or expire.- Removed plugins: taking a plugin out of your code doesn't delete its rows.
Here is an example for PostgreSQL. Run it on a schedule, choose your own windows and test it in staging first.
-- Audit rows older than 90 days
DELETE FROM corsair_events
WHERE created_at < NOW() - INTERVAL '90 days';
-- Finished approval requests older than 30 days
DELETE FROM corsair_permissions
WHERE status IN ('completed', 'denied', 'expired', 'failed')
AND updated_at < NOW() - INTERVAL '30 days';
-- Remove one tenant's synced records and audit rows when they leave
DELETE FROM corsair_entities
WHERE account_id IN (
SELECT id FROM corsair_accounts WHERE tenant_id = $1
);
DELETE FROM corsair_events
WHERE account_id IN (
SELECT id FROM corsair_accounts WHERE tenant_id = $1
);
Choose windows by purpose. Audit rows may need to live longer than approval rows, and some teams keep a scrubbed copy of audit data in a separate store. The idea matches the data minimisation and storage limitation principles in privacy law: keep what you can justify, for as long as you can justify it. The cost of Corsair's approach is that the docs describe no automatic pruning, so put the job in place before the first production tenant.
Who Can Read Sensitive Records, and What Can an Agent Do With Them?
In Corsair, access has two layers: what an agent is allowed to do, which Corsair enforces, and who can read the tables, which your database permissions enforce. n8n handles who can see execution data mainly through roles and a reveal permission inside the platform.
Corsair's controls:
- Tenant scoping: with
multiTenancy: true, calls must go throughwithTenant(), which the docs say is enforced at the type level. Corsair adds the tenant ID to every insert and select in its database adapter, and credentials are retrieved for that tenant only. That scoping applies to the normal API, so raw SQL against the tables needs its own tenant filter. - Permission modes: every endpoint has a risk level (read, write or destructive), and permission modes map risk to a policy.
openallows everything,cautiousneeds approval for destructive calls,strictneeds approval for writes and blocks destructive calls, andreadonlyallows only reads. Per endpoint overrides win over the mode, and adenyleaves no database record. - Single use approvals: an approved action runs once with the frozen arguments and can't be replayed.
- Database access: give only the services that need them a role that can read
corsair_eventsandcorsair_permissions, and keep the agent's own runtime away from direct database credentials.
Keep in mind that approval screens show arguments, so a reviewer sees what the agent wanted to send. Treat reviewers as readers of customer data.
In n8n, instance roles (owner, admin, member) and project roles (admin, editor, viewer) control who sees what. Project roles are available on all plans except Community, and custom roles need Enterprise. Users with the execution:reveal scope can reveal redacted data, which instance owners and admins have by default. Each reveal and each denied attempt is emitted as an audit event through log streaming, and reveal is blocked for executions that used dynamic credentials.
What Do Engineers Still Need to Troubleshoot Without Raw Payloads?
Identifiers, states and error categories: enough to say which tenant, which integration, which call, what happened and when, without any customer content. Both tools keep this kind of evidence even when payloads are hidden or never stored.
In Corsair:
corsair_events: an account ID (which links to the tenant throughcorsair_accounts), an event type, a status of pending, processing, completed or failed, and timestamps.corsair_permissions: plugin, endpoint, tenant ID, a status that moves through pending, approved, executing, completed, denied, expired or failed, an expiry time and an error field when a run fails.- Typed errors:
AuthMissingErrorandReconnectRequiredErrortell you a tenant has no usable credential or a stored connection went stale, and they carry a connect URL. - Error handler context: the handler receives the error plus the operation, which is enough to log which call failed.
In n8n, a redacted execution still shows node names, status, timing and workflow structure, and errors keep their type and HTTP status. The Error Trigger receives the execution ID and URL, the workflow name, the last node executed and the error message.
Logging and telemetry guidance from OWASP and OpenTelemetry points to the same shape. A troubleshooting record that doesn't expose customers usually has:
- A correlation ID you generate and pass through every step
- A tenant ID or hashed user ID rather than names or emails
- Plugin and endpoint names
- Error class, provider HTTP status and retry count
- UTC timestamps and durations
Customer content is the one thing missing, and you can work without it. When you need to reproduce a failure, use a synthetic record in staging instead of reading the customer's.
Where Can Sensitive Data Still Slip Through?
In the places no redaction feature covers: your own logging, approval screens, backups and the monitoring tools around the integration layer. Both tools have the same class of gap.
- Console output in hooks: Corsair's hook examples log arguments and results with
console.log, and those lines go straight to your application logs. Don't logctx.optionseither, because it holds plugin configuration. n8n's docs flag the same pattern: Code nodeconsole.logoutput isn't redacted. - Error strings: the docs' handler examples log
error.message, and provider errors can include the value that was rejected. - Approval arguments: they are stored as JSON text in
corsair_permissions.args, shown to reviewers and held briefly by Hub during review. The docs describe no field masking there, so keep gated calls and reviewer access narrow. - Backups and replicas: Corsair encrypts credentials, but entity data and event payloads are ordinary JSON columns, and every backup copy inherits them. n8n's docs say the same of its database: redaction doesn't change stored data.
- Joined tables: the docs encourage foreign keys to
corsair_entities, so customer data also appears in your own tables. Include them in deletion and retention plans. - Monitoring tools: anything you export to APM or log platforms leaves your database and your retention rules behind.
A test that catches most of these: plant a canary, such as a fake customer email, in a test record. Run the flow, including a failure and an approval. Then search every table, log file, backup and monitoring tool for the canary. Repeat it after each change to hooks or logging.
Keeping sensitive data out of integration logs comes down to knowing where each record lives and who sets the rules for it. Corsair is an open source MCP integration layer for AI agents that keeps your integration records in your own database, encrypts credentials under keys you control, scopes every call by tenant and gates risky actions behind approval. Redaction and retention stay in code and database jobs you own, which gives you control over what your agents leave behind. To see how it fits your stack, explore Corsair and start with the docs.
FAQs
Does Corsair redact sensitive data automatically?
Corsair encrypts stored credentials by default, but its docs describe no built in switch that redacts customer fields from stored records or logs. You write those rules in hooks and error handlers, and you apply retention and access rules in your database. n8n differs here: it offers execution data redaction as an Enterprise feature that hides payloads in its execution viewer.
Where does Corsair store integration data?
In your own database, in the same five tables for every integration: corsair_integrations, corsair_accounts, corsair_entities, corsair_events and corsair_permissions. Corsair works through your existing connection, with PostgreSQL, SQLite and the common ORMs supported.
How long does Corsair keep event and approval records?
The docs describe no retention setting, so they stay until you delete them. Entity rows are updated in place, so they don't pile up the same way. Schedule purge jobs for corsair_events and finished rows in corsair_permissions, and delete a tenant's rows when they leave.
Can an AI agent read the data Corsair stores?
Agents act through the operations your app exposes, and typed database reads are scoped to the active tenant through withTenant(). The docs don't describe agents querying corsair_events or corsair_permissions. Keep the agent's runtime without direct database credentials, and use permission modes to control which actions it can take.
Does Corsair Hub see my customers' data?
Hub stores none of your tenants' tokens and never touches your database, so synced records, events and approvals live in yours. During an approval, Hub keeps a short session with the plugin, endpoint, call arguments and tenant ID until the request is decided or expires, so treat those arguments as visible to the review step.