feat: add Adrapid plugin - #653
Conversation
|
@Vishuzz is attempting to deploy a commit to the corsair Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThe PR adds the ChangesAdrapid plugin
Provider registration
Import cleanup
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant AdrapidPlugin
participant exampleGet
participant makeAdrapidRequest
participant AdrapidAPI
Caller->>AdrapidPlugin: invoke registered endpoint
AdrapidPlugin->>exampleGet: validate input and run handler
exampleGet->>makeAdrapidRequest: send id and API key
makeAdrapidRequest->>AdrapidAPI: issue configured GET request
AdrapidAPI-->>makeAdrapidRequest: return response or error
makeAdrapidRequest-->>exampleGet: return typed result
exampleGet-->>Caller: log completion and return response
Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Comment Warning |
Greptile SummaryThis PR adds a generated Adrapid plugin package and also registers You.com in the core provider vocabulary. The Adrapid implementation is not production-ready and the unrelated You.com, demo, and website edits violate the enforced single-plugin PR boundary.
Confidence Score: 0/5This PR is not safe to merge until the scope violations, placeholder Adrapid implementation, missing endpoint coverage, and unauthenticated webhook path are fixed. The deterministic plugin gate rejects the changed-file footprint, the only API endpoint targets a placeholder host, and the webhook handler accepts forged events because signature verification always succeeds. Files Needing Attention: packages/adrapid/client.ts, packages/adrapid/webhooks/types.ts, packages/adrapid/endpoints/example.ts, packages/adrapid/schema.test.ts, packages/youcom/jest.config.cjs, demo/mcp/corsair.ts, www/src/components/landing/menu/site-menu.tsx
|
| Filename | Overview |
|---|---|
| packages/adrapid/client.ts | Adds the plugin HTTP client but retains api.example.com, so the only endpoint cannot contact Adrapid. |
| packages/adrapid/index.ts | Wires auth, endpoint, and webhook configuration, but provider matching trusts signature-header presence and several provider-specific fields remain placeholders. |
| packages/adrapid/webhooks/types.ts | Defines webhook schemas and matching helpers but unconditionally accepts every webhook signature. |
| packages/adrapid/endpoints/example.ts | Adds a generated example endpoint that targets the placeholder client and has no corresponding endpoint test. |
| packages/adrapid/schema.test.ts | Tests only schema metadata and does not exercise the implemented endpoint or webhook behavior. |
| packages/corsair/core/constants.ts | Consistently registers Adrapid and You.com in provider IDs, display names, and the provider union. |
| packages/youcom/jest.config.cjs | Adds useful core and hub aliases, but changing a second plugin makes this Adrapid PR fail the scope gate. |
| demo/mcp/corsair.ts | Removes an unused Gmail import, but this unrelated edit is prohibited in a plugin PR. |
| www/src/components/landing/menu/site-menu.tsx | Removes an unused icon import, but this unrelated website edit is prohibited in a plugin PR. |
Sequence Diagram
sequenceDiagram
participant Attacker
participant Router as Webhook Router
participant Matcher as Adrapid Matchers
participant Handler as Example Handler
participant Events as Corsair Events
Attacker->>Router: x-adrapid-signature + example payload
Router->>Matcher: Identify provider and tenant
Matcher-->>Router: Match from header/body
Router->>Handler: Dispatch parsed payload
Handler->>Handler: verifyAdrapidWebhookSignature()
Note over Handler: Always returns valid: true
Handler->>Events: Log completed event
Handler-->>Attacker: success: true
Reviews (1): Last reviewed commit: "feat: add Adrapid plugin" | Re-trigger Greptile
| '^corsair/core$': '<rootDir>/../corsair/core.ts', | ||
| '^corsair/hub$': '<rootDir>/../corsair/hub.ts', |
There was a problem hiding this comment.
This Adrapid PR also changes the You.com plugin, a demo file, and a website file. The deterministic plugin gate treats this as both a multi-plugin change and an out-of-scope change, causing the required check to fail and block the PR from merging.
Rule Used: A plugin PR must only modify files inside a single... (source)
Knowledge Base Used: The provider-plugin package pattern
| } | ||
|
|
||
| // TODO: Update with your API base URL | ||
| const ADRAPID_API_BASE = 'https://api.example.com'; |
There was a problem hiding this comment.
Endpoint uses placeholder API host
When a consumer invokes adrapid.api.example.get, the client sends the request to https://api.example.com rather than an Adrapid API. The plugin's only endpoint therefore fails instead of returning the requested provider resource.
Rule Used: Flag boilerplate residue from the plugin generator... (source)
Knowledge Base Used: The provider-plugin package pattern
| secret: string, | ||
| ): { valid: boolean; error?: string } { | ||
| // TODO: Implement webhook signature verification | ||
| return { valid: true }; |
There was a problem hiding this comment.
Webhook authentication always succeeds
When a request supplies the expected header and an example payload, this verifier ignores both the request and secret and returns valid: true, causing an unauthenticated event to be accepted and logged as a completed Adrapid webhook. How this was verified: The request path checks only for header presence before dispatch, and the handler relies on this unconditional result before logging the event.
Rule Used: Flag boilerplate residue from the plugin generator... (source)
Knowledge Base Used: The provider-plugin package pattern
| const response = await makeAdrapidRequest< | ||
| AdrapidEndpointOutputs['exampleGet'] | ||
| >(`example/${input.id}`, ctx.key, { method: 'GET' }); | ||
|
|
There was a problem hiding this comment.
Endpoint has no behavioral test
The package's only test checks schema metadata and never invokes this implemented endpoint. As a result, the placeholder host and request behavior pass the package test suite without any endpoint assertion, contrary to the required plugin test coverage.
Rule Used: Plugin packages must include at least one *.test.t... (source)
Knowledge Base Used: The provider-plugin package pattern
Plugin PR scorecard —
|
| Check | Status | Notes |
|---|---|---|
| R1 — Scope: one plugin per PR | ❌ | This PR touches: adrapid, youcom |
| R2 — Tests with assertions | ✅ | |
| R3 — Description | ❌ | Description section is empty or placeholder |
| R3 — Linked issue / claim | No "Fixes #…" or claim link — add one if this PR has a claim or issue | |
| R4 — Demo video / recording | ❌ | Required in "Screenshots / Demos" before a maintainer reviews |
Rules: PLUGIN_PR_RULES.md · re-runs on every push
|
Hey @Vishuzz, thanks for the contribution! 🏴☠️ Before a maintainer reviews, please fix the items below — the review re-runs automatically on your next push. Must fix
Rule Used: A plugin PR must only modify files inside a single... (source) Knowledge Base Used: The provider-plugin package pattern
Rule Used: Flag boilerplate residue from the plugin generator... (source) Knowledge Base Used: The provider-plugin package pattern
Rule Used: Flag boilerplate residue from the plugin generator... (source) Knowledge Base Used: The provider-plugin package pattern
Rule Used: Plugin packages must include at least one *.test.t... (source) Knowledge Base Used: The provider-plugin package pattern PR requirements (rules)
If anything remains after your next push, a bot commit will clean it up; a maintainer always does the final review and merge. |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/adrapid/client.ts`:
- Around line 14-15: Replace the placeholder value of ADRAPID_API_BASE with the
documented Adrapid API base URL, removing the example endpoint before plugin
registration.
- Around line 54-58: Update the ADRAPID_API_BASE constant to the actual Adrapid
API base URL instead of the placeholder. In the error handling around the client
request, re-throw existing ApiError instances before wrapping other Error
values, preserving their status and retryAfter metadata; retain the
unknown-error fallback for non-Error values.
In `@packages/adrapid/endpoints/example.ts`:
- Around line 4-17: Validate the response in the get endpoint using
AdrapidEndpointOutputSchemas.exampleGet immediately after makeAdrapidRequest and
before logEventFromContext. Use the parsed result as the returned response, so
malformed provider data is rejected before consumers receive it or the completed
event is logged.
In `@packages/adrapid/index.ts`:
- Around line 115-122: Determine AdRapid’s stable account identifier and use
that single link type consistently: update adrapidAuthConfig in
packages/adrapid/index.ts (115-122), the webhook matcher in
packages/adrapid/webhooks/tenant-matcher.ts (17-24), and the OAuth resolver in
packages/adrapid/webhooks/oauth-tenant-link.ts (9-30). Ensure OAuth tokens
resolve and store the webhook link using the provider’s webhook
data.id-compatible identifier, and remove the placeholder URL and TODO stubs.
In `@packages/adrapid/webhooks/types.ts`:
- Around line 56-62: The verifyAdrapidWebhookSignature function must perform
real authentication instead of always returning valid. Implement the configured
signature scheme using request.rawBody and the provided secret, reject missing
or malformed signatures or secrets, and compare HMAC signatures with a
timing-safe method; add tests covering valid, invalid, missing, and malformed
signatures.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f250a97f-ebea-4af0-a362-a6c4277d2359
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (22)
demo/mcp/corsair.tspackages/adrapid/client.tspackages/adrapid/endpoints/example.tspackages/adrapid/endpoints/index.tspackages/adrapid/endpoints/types.tspackages/adrapid/error-handlers.tspackages/adrapid/index.tspackages/adrapid/jest.config.cjspackages/adrapid/package.jsonpackages/adrapid/schema.test.tspackages/adrapid/schema/database.tspackages/adrapid/schema/index.tspackages/adrapid/tsconfig.jsonpackages/adrapid/tsup.config.tspackages/adrapid/webhooks/example.tspackages/adrapid/webhooks/index.tspackages/adrapid/webhooks/oauth-tenant-link.tspackages/adrapid/webhooks/tenant-matcher.tspackages/adrapid/webhooks/types.tspackages/corsair/core/constants.tspackages/youcom/jest.config.cjswww/src/components/landing/menu/site-menu.tsx
💤 Files with no reviewable changes (1)
- demo/mcp/corsair.ts
| // TODO: Update with your API base URL | ||
| const ADRAPID_API_BASE = 'https://api.example.com'; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Replace the placeholder API base URL before release.
Every endpoint request uses https://api.example.com. The Adrapid endpoint cannot reach the provider with this value. Configure the documented Adrapid API base URL before registering this plugin.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/adrapid/client.ts` around lines 14 - 15, Replace the placeholder
value of ADRAPID_API_BASE with the documented Adrapid API base URL, removing the
example endpoint before plugin registration.
| } catch (error) { | ||
| if (error instanceof Error) { | ||
| throw new AdrapidAPIError(error.message); | ||
| } | ||
| throw new AdrapidAPIError('Unknown error'); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline packages/adrapid --items all --type class,function
rg -n -C 5 'makeAdrapidRequest|instanceof ApiError|retryAfter|errorHandlers' packages/adrapidRepository: corsairdev/corsair
Length of output: 6371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- packages/adrapid/client.ts ---'
cat -n packages/adrapid/client.ts
printf '%s\n' '--- packages/adrapid/error-handlers.ts ---'
cat -n packages/adrapid/error-handlers.ts
printf '%s\n' '--- ApiError definition and request implementation ---'
rg -n -C 8 'class ApiError|export class ApiError|function request|const request|retryAfter' packages --glob '*.{ts,tsx,js,jsx}'Repository: corsairdev/corsair
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- Adrapid client ---'
sed -n '1,110p' packages/adrapid/client.ts
printf '%s\n' '--- Adrapid handlers ---'
sed -n '1,80p' packages/adrapid/error-handlers.ts
printf '%s\n' '--- Core ApiError/request locations ---'
rg -l 'export class ApiError|class ApiError|export async function request|async function request' packages/corsair --glob '*.{ts,tsx}' \
| head -n 30
printf '%s\n' '--- Core ApiError/request definitions ---'
rg -n -C 12 'export class ApiError|class ApiError|export async function request|async function request' packages/corsair --glob '*.{ts,tsx}' \
| head -n 240Repository: corsairdev/corsair
Length of output: 4514
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- ApiError implementation ---'
sed -n '100,180p' packages/corsair/async-core/ApiError.ts
printf '%s\n' '--- corsair/http exports ---'
rg -n -C 5 'ApiError|request' packages/corsair --glob 'index.ts' --glob '*http*' --glob '*request*' \
| head -n 240
printf '%s\n' '--- request call and error construction ---'
rg -n -C 8 'new ApiError|request<|request\(' packages/corsair/async-core packages/corsair/http --glob '*.{ts,tsx}' \
| head -n 300Repository: corsairdev/corsair
Length of output: 19300
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- corsair/http export path ---'
fd -i 'http|index.ts|package.json' packages/corsair | head -n 120
rg -n -C 4 'async-core/(ApiError|request)|from .*ApiError|export .*ApiError|corsair/http' packages/corsair packages/adrapid \
--glob '*.{ts,tsx,json}' | head -n 220
printf '%s\n' '--- Static behavior check ---'
python3 - <<'PY'
from pathlib import Path
client = Path("packages/adrapid/client.ts").read_text()
handlers = Path("packages/adrapid/error-handlers.ts").read_text()
request = Path("packages/corsair/async-core/request.ts").read_text()
api_error = Path("packages/corsair/async-core/ApiError.ts").read_text()
checks = {
"client wraps Error": "throw new AdrapidAPIError(error.message)" in client,
"client does not import ApiError": "ApiError" not in client.split("export async function", 1)[0],
"ApiError exposes status": "public readonly status: number;" in api_error,
"ApiError exposes retryAfter": "public readonly retryAfter?: number;" in api_error,
"request throws ApiError": "throw new ApiError(options, result" in request,
"rate handler checks ApiError": "error instanceof ApiError && error.status === 429" in handlers,
"rate handler reads retryAfter": "error instanceof ApiError && error.retryAfter !== undefined" in handlers,
"auth handler checks ApiError": "error instanceof ApiError && error.status === 401" in handlers,
"placeholder base URL remains": "https://api.example.com" in client,
}
for name, passed in checks.items():
print(f"{name}: {'yes' if passed else 'no'}")
PYRepository: corsairdev/corsair
Length of output: 11618
Use the Adrapid API URL and preserve ApiError metadata.
- Replace
ADRAPID_API_BASE = 'https://api.example.com'with the Adrapid API base URL. Current requests target a placeholder. - Re-throw
ApiErrorbefore wrapping other errors. The wrapper removesstatusandretryAfter, so the error handlers cannot use status checks or the server-provided retry delay.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/adrapid/client.ts` around lines 54 - 58, Update the ADRAPID_API_BASE
constant to the actual Adrapid API base URL instead of the placeholder. In the
error handling around the client request, re-throw existing ApiError instances
before wrapping other Error values, preserving their status and retryAfter
metadata; retain the unknown-error fallback for non-Error values.
| import type { AdrapidEndpointOutputs } from './types'; | ||
|
|
||
| export const get: AdrapidEndpoints['exampleGet'] = async (ctx, input) => { | ||
| const response = await makeAdrapidRequest< | ||
| AdrapidEndpointOutputs['exampleGet'] | ||
| >(`example/${input.id}`, ctx.key, { method: 'GET' }); | ||
|
|
||
| await logEventFromContext( | ||
| ctx, | ||
| 'adrapid.example.get', | ||
| { ...input }, | ||
| 'completed', | ||
| ); | ||
| return response; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline packages/adrapid --items all --type function
rg -n -C 4 'makeAdrapidRequest|AdrapidEndpointOutputSchemas|\.parse\(' packages/adrapidRepository: corsairdev/corsair
Length of output: 4095
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- packages/adrapid/client.ts ---'
cat -n packages/adrapid/client.ts
printf '%s\n' '--- packages/adrapid/endpoints/types.ts ---'
cat -n packages/adrapid/endpoints/types.ts
printf '%s\n' '--- packages/adrapid/endpoints/example.ts ---'
cat -n packages/adrapid/endpoints/example.ts
printf '%s\n' '--- endpoint registration and invocation ---'
rg -n -C 6 'adrapidEndpointSchemas|adrapidEndpointsNested|exampleGet|RequiredPluginEndpointSchemas|output.*parse|parse.*output' packages/adrapid packages/corsairRepository: corsairdev/corsair
Length of output: 15720
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- endpoint schema consumers ---'
rg -n -C 8 'endpointSchemas|\.output\b|outputSchema|safeParse|parseAsync|safeParseAsync' packages/corsair packages --glob '*.{ts,tsx,js,jsx}' | head -n 500
printf '%s\n' '--- endpoint execution definitions ---'
rg -n -C 10 'execute.*endpoint|invoke.*endpoint|endpoints.*schemas|EndpointTree|CorsairEndpoint' packages/corsair/core packages/corsair --glob '*.{ts,tsx}'Repository: corsairdev/corsair
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- all endpointSchemas references ---'
rg -l 'endpointSchemas' packages --glob '*.{ts,tsx,js,jsx}' | sort
printf '%s\n' '--- schema parsing references in Corsair core ---'
rg -n 'safeParse|parseAsync|safeParseAsync|\.parse\(' packages/corsair/core packages/corsair/http packages/corsair/client packages/corsair --glob '*.{ts,tsx}' --glob '!tests/**' | head -n 300
printf '%s\n' '--- binding and plugin construction ---'
rg -n -C 12 'bindEndpoints|function bind|const bind|endpoints:' packages/corsair/core packages/corsair --glob '*.{ts,tsx}' --glob '!tests/**' | head -n 500Repository: corsairdev/corsair
Length of output: 7702
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- Corsair core files ---'
git ls-files packages/corsair/core | sed -n '1,240p'
printf '%s\n' '--- endpoint schema references in core ---'
rg -n -C 12 'endpointSchemas' packages/corsair/core
printf '%s\n' '--- endpoint binding references ---'
rg -n -C 10 'bindEndpoints|BoundEndpointFn|enforcePermission|endpointPath' packages/corsair --glob '*.{ts,tsx}' --glob '!tests/**' | head -n 500Repository: corsairdev/corsair
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
example = Path("packages/adrapid/endpoints/example.ts").read_text()
client = Path("packages/adrapid/client.ts").read_text()
plugins = Path("packages/corsair/core/plugins/index.ts").read_text()
inspect = Path("packages/corsair/core/inspect/index.ts").read_text()
bind = Path("packages/corsair/core/endpoints/bind.ts").read_text()
checks = {
"endpoint calls typed request helper": "makeAdrapidRequest<" in example,
"endpoint imports output schema": "AdrapidEndpointOutputSchemas" in example,
"endpoint parses response": ".parse(" in example or ".safeParse(" in example,
"helper returns request result directly": "return await request<T>" in client,
"plugin docs describe schemas as inspection metadata": "Used by get_schema()" in plugins,
"inspection converts output schema to docs": "zodToFormSchema(schemas.output)" in inspect,
"binding layer references endpoint schemas": "endpointSchemas" in bind,
}
for name, result in checks.items():
print(f"{name}: {result}")
assert checks["endpoint calls typed request helper"]
assert not checks["endpoint imports output schema"]
assert not checks["endpoint parses response"]
assert checks["helper returns request result directly"]
assert checks["plugin docs describe schemas as inspection metadata"]
assert checks["inspection converts output schema to docs"]
assert not checks["binding layer references endpoint schemas"]
PYRepository: corsairdev/corsair
Length of output: 468
Validate the provider response before logging completion.
makeAdrapidRequest<AdrapidEndpointOutputs['exampleGet']> provides only a static type. Parse the response with AdrapidEndpointOutputSchemas.exampleGet so malformed provider data cannot reach consumers or produce a completed event.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/adrapid/endpoints/example.ts` around lines 4 - 17, Validate the
response in the get endpoint using AdrapidEndpointOutputSchemas.exampleGet
immediately after makeAdrapidRequest and before logEventFromContext. Use the
parsed result as the returned response, so malformed provider data is rejected
before consumers receive it or the completed event is logged.
| export const adrapidAuthConfig = { | ||
| api_key: { | ||
| account: ['tenant_external_id'] as const, | ||
| }, | ||
| oauth_2: { | ||
| account: ['tenant_external_id'] as const, | ||
| }, | ||
| } as const satisfies PluginAuthConfig; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- plugin files ---'
git ls-files 'packages/adrapid/**' '.github/PLUGIN_PR_RULES.md' 'packages/corsair/core/constants.ts'
printf '%s\n' '--- relevant source ---'
for f in packages/adrapid/index.ts \
packages/adrapid/webhooks/tenant-matcher.ts \
packages/adrapid/webhooks/oauth-tenant-link.ts; do
if [ -f "$f" ]; then
printf '\n### %s\n' "$f"
cat -n "$f"
fi
done
printf '%s\n' '--- adrapid references ---'
rg -n -C 3 'tenant_external_id|tenant-matcher|oauth-tenant-link|adrapid' packages .github \
-g '!node_modules' -g '!dist' -g '!build' || true
printf '%s\n' '--- plugin rules ---'
if [ -f .github/PLUGIN_PR_RULES.md ]; then
cat -n .github/PLUGIN_PR_RULES.md
fiRepository: corsairdev/corsair
Length of output: 50374
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- plugin rules ---'
cat -n .github/PLUGIN_PR_RULES.md
printf '%s\n' '--- adrapid package metadata and docs ---'
for f in packages/adrapid/package.json packages/adrapid/schema/database.ts packages/adrapid/schema/index.ts packages/adrapid/webhooks/types.ts packages/adrapid/schema.test.ts; do
printf '\n### %s\n' "$f"
cat -n "$f"
done
find packages/adrapid -maxdepth 2 -type f \( -iname 'README*' -o -iname '*.md' \) -print
printf '%s\n' '--- comparable tenant implementations ---'
for f in packages/{slack,gitlab,github,hubspot,linear}/webhooks/tenant-matcher.ts \
packages/{slack,gitlab,github,hubspot,linear}/webhooks/oauth-tenant-link.ts; do
if [ -f "$f" ]; then
printf '\n### %s\n' "$f"
cat -n "$f"
fi
done
printf '%s\n' '--- core tenant-link call sites and types ---'
rg -n -C 5 'oauthWebhookTenantLinkResolver|pluginTenantWebhookMatcher|WebhookTenantMatch|authConfig\.account|tenant_external_id' packages/corsair packages -g '*.ts' -g '*.tsx' \
| head -n 1200Repository: corsairdev/corsair
Length of output: 50375
🌐 Web query:
Adrapid API OAuth webhook tenant identifier tenant_external_id webhook payload
💡 Result:
The Adrapid platform provides a REST API primarily authenticated via bearer tokens [1][2] and supports OAuth-based integration through third-party platforms [3]. While Adrapid documentation outlines tools for managing banners, templates, and user access [2][4], there is no public technical documentation confirming a standardized internal API schema that utilizes a specific "tenant_external_id" field within a native webhook payload. For integrations involving Adrapid, tenant management and webhook handling are typically managed through the following mechanisms: 1. Integration Layer: Platforms that integrate with Adrapid (such as automation tools or middleware) often abstract the webhook handling [3][5]. In these environments, identifiers like tenant IDs are managed by the integration provider's own infrastructure rather than natively as a required field in the Adrapid API request payload [5]. 2. Administrative API: Access and user-specific configurations are handled via the Adrapid administrative API, which includes endpoints for retrieving user-specific access URLs and widget configurations [4]. 3. Webhook Handling: If you are implementing custom webhooks to receive notifications from Adrapid, you are responsible for defining the endpoint. Since Adrapid documentation does not explicitly document a "tenant_external_id" as a mandatory part of its outgoing webhook payload, you should inspect the raw JSON payload sent to your defined endpoint to determine which identifiers (such as user ID or account ID) are available for your mapping needs. If you are developing a multi-tenant application, it is standard practice to map the Adrapid User ID or an internal account identifier to your own "tenant_external_id" within your database upon receiving a webhook, rather than expecting that specific field to be natively present in the payload. [1][4][5]
Citations:
- 1: https://docs.adrapid.com/api/overview
- 2: https://docs.adrapid.com/api/tutorials/create-images-from-catalog
- 3: https://relevanceai.com/integrations/adrapid
- 4: https://docs.adrapid.com/enterprise/whitelabel/widget
- 5: https://dialnexa.mintlify.app/integrations/adrapid
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- OAuth link persistence path ---'
sed -n '1,180p' packages/corsair/webhooks/resolve-oauth-tenant-link.ts
sed -n '1,280p' packages/corsair/webhooks/tenant-links.ts
printf '%s\n' '--- exact tenant-link type and auth configuration definitions ---'
rg -n -A 18 -B 8 'export type WebhookTenantMatch|type WebhookTenantMatch|export type PluginAuthConfig|interface PluginAuthConfig|account\??:' packages/corsair/core packages/corsair -g '*.ts' | head -n 500
printf '%s\n' '--- adrapid TODO and placeholder inventory ---'
rg -n 'TODO|api\.example\.com|tenant_external_id|example' packages/adrapid
printf '%s\n' '--- tracked test inventory for adrapid ---'
git ls-files 'packages/adrapid/**/*test.ts' 'packages/adrapid/**/*.test.ts' | sort
printf '%s\n' '--- bounded behavioral probe of the shown extractors ---'
python3 - <<'PY'
from typing import Any
def first_string(values):
return next((v for v in values if isinstance(v, str) and v), None)
def as_record(value):
return value if isinstance(value, dict) else None
def matcher(body: Any):
if not isinstance(body, dict):
return None
external_id = first_string([
body.get("tenant_external_id"),
(as_record(body.get("data")) or {}).get("tenant_external_id"),
])
return None if not external_id else {
"linkType": "tenant_external_id",
"externalId": external_id,
}
def resolver(tokens: dict):
value = tokens.get("tenant_external_id")
external_id = value if isinstance(value, (str, int, float)) and not isinstance(value, bool) and str(value) else None
if external_id is not None:
return {"linkType": "tenant_external_id", "externalId": str(external_id)}
if not tokens.get("access_token"):
return None
return None
cases = [
("documented example payload", {"type": "example", "created_at": "x", "data": {"id": "acct-1"}}),
("placeholder top-level field", {"tenant_external_id": "acct-1"}),
("placeholder nested field", {"data": {"tenant_external_id": "acct-1"}}),
]
for name, body in cases:
print(name, "=>", matcher(body))
for name, tokens in [
("token has no placeholder id", {"access_token": "token"}),
("token has placeholder id", {"access_token": "token", "tenant_external_id": "acct-1"}),
("token has no access token", {}),
]:
print(name, "=>", resolver(tokens))
PYRepository: corsairdev/corsair
Length of output: 49034
Implement provider-specific tenant linkage before enabling tenant routing.
adrapidAuthConfig registers the placeholder tenant_external_id. The matcher only reads that placeholder, while the webhook schema defines data.id. When OAuth tokens omit tenant_external_id, the resolver returns null, so OAuth does not store a webhook link. Determine the provider’s stable account identifier, then use the same link type in adrapidAuthConfig, the webhook matcher, and the OAuth resolver. Remove the placeholder URL and TODO stubs.
📍 Affects 3 files
packages/adrapid/index.ts#L115-L122(this comment)packages/adrapid/webhooks/tenant-matcher.ts#L17-L24packages/adrapid/webhooks/oauth-tenant-link.ts#L9-L30
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/adrapid/index.ts` around lines 115 - 122, Determine AdRapid’s stable
account identifier and use that single link type consistently: update
adrapidAuthConfig in packages/adrapid/index.ts (115-122), the webhook matcher in
packages/adrapid/webhooks/tenant-matcher.ts (17-24), and the OAuth resolver in
packages/adrapid/webhooks/oauth-tenant-link.ts (9-30). Ensure OAuth tokens
resolve and store the webhook link using the provider’s webhook
data.id-compatible identifier, and remove the placeholder URL and TODO stubs.
| export function verifyAdrapidWebhookSignature( | ||
| request: WebhookRequest<AdrapidWebhookPayload>, | ||
| secret: string, | ||
| ): { valid: boolean; error?: string } { | ||
| // TODO: Implement webhook signature verification | ||
| return { valid: true }; | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect Corsair webhook request types and existing signature-verification patterns.
rg -n -C 5 --glob '*.ts' \
'verify[A-Za-z]*WebhookSignature|createHmac|timingSafeEqual|RawWebhookRequest|WebhookRequest|pluginWebhookMatcher' \
packagesRepository: corsairdev/corsair
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- Adrapid files ---'
git ls-files packages/adrapid
printf '%s\n' '--- Adrapid webhook implementation and callers ---'
rg -n -C 12 \
'verifyAdrapidWebhookSignature|AdrapidWebhookPayload|event log|logEventFromContext|webhooks' \
packages/adrapid
printf '%s\n' '--- Core webhook request types and processing ---'
rg -n -C 12 \
'export (type|interface) (RawWebhookRequest|WebhookRequest)|type RawWebhookRequest|interface RawWebhookRequest|function processWebhook|async function processWebhook' \
packages/corsair packages/adrapidRepository: corsairdev/corsair
Length of output: 16148
🌐 Web query:
Adrapid webhook signature verification documentation header HMAC raw body
💡 Result:
There is no publicly available documentation from Adrapid regarding a native webhook signature verification mechanism or specific HMAC header requirements [1][2][3]. Adrapid's current API documentation primarily focuses on REST API endpoints for banner creation and account management, using Bearer token authentication [1][2]. While integrations with third-party automation platforms (such as Zapier) exist, these typically manage authentication and event delivery through the platform's own infrastructure [4]. If you are implementing a custom webhook receiver for Adrapid (or a service calling it), you should follow standard industry practices for HMAC-SHA256 signature verification [5][6][7]: 1. Capture the Raw Body: You must read the incoming request body as raw bytes before any JSON parsing occurs [8][9][7]. If your web framework (e.g., Express) parses the body automatically, re-serializing it will change the byte structure, causing verification to fail [9][6]. 2. Identify the Signature Header: Since no official Adrapid documentation specifies a signature header, you would need to determine if one is provided by the specific integration source or platform you are using [5][6]. Common header names include X-Hub-Signature-256 or X-Webhook-Signature [5][6]. 3. Compute the Expected HMAC: Use the shared secret key to compute an HMAC-SHA256 hash over the captured raw request body [8][9]. 4. Perform Constant-Time Comparison: Compare the computed hash with the hash received in the header using a timing-safe comparison function (e.g., crypto.timingSafeEqual in Node.js) to prevent timing attacks [8][5][7]. If you require webhooks from Adrapid, it is recommended to check the specific integration settings or dashboard of the tool you are using to facilitate the webhook, as that platform may define its own verification protocol [10].
Citations:
- 1: https://docs.adrapid.com/api/overview
- 2: https://docs.adrapid.com/api/tutorials/create-images-from-catalog
- 3: https://docs.adrapid.com/
- 4: https://zapier.com/apps/adrapid/integrations
- 5: https://www.fasthook.io/webhooks/signature-verification
- 6: https://webhooks.cc/docs/guides/verify-webhook-signatures
- 7: https://hookcap.dev/blog/how-to-secure-webhooks-hmac-verification/
- 8: https://adaptocms.com/docs/integrating-webhooks/
- 9: https://dev.to/137foundry/step-by-step-webhook-signature-verification-for-any-sender-2nje
- 10: https://dialnexa.mintlify.app/integrations/adrapid
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- Adrapid webhook types ---'
cat -n packages/adrapid/webhooks/types.ts
printf '%s\n' '--- Adrapid plugin configuration and key resolution ---'
sed -n '1,220p' packages/adrapid/index.ts
rg -n -C 10 'webhookSecret|ctx\.key|get_integration_credentials|keyBuilder|webhookHooks' \
packages/adrapid packages/corsair
printf '%s\n' '--- Webhook request construction and handler invocation ---'
sed -n '176,330p' packages/corsair/webhooks/index.ts
rg -n -C 15 'rawBody|WebhookRequest<' packages/corsair/webhooks packages/corsair/core
printf '%s\n' '--- Relevant existing verifier implementations ---'
sed -n '1,120p' packages/corsair/async-core/webhook-utils.ts
sed -n '240,290p' packages/instagram/webhooks/types.ts
fd -i 'types.ts' packages/github/webhooks packages/xquik/webhooks packages/stripe/webhooks 2>/dev/null \
| xargs -r rg -n -C 12 'verify.*Signature|rawBody|signature'Repository: corsairdev/corsair
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
paths = [
Path("packages/adrapid/webhooks/types.ts"),
Path("packages/adrapid/webhooks/example.ts"),
Path("packages/adrapid/index.ts"),
Path("packages/corsair/core/webhooks/index.ts"),
Path("packages/corsair/webhooks/index.ts"),
]
for path in paths:
text = path.read_text()
print(f"{path}:")
for term in ("TODO: Implement webhook signature verification",
"return { valid: true }",
"verifyAdrapidWebhookSignature(request, ctx.key)",
"rawBody?: string",
"webhookSecret"):
print(f" {term!r}: {text.count(term)}")
PYRepository: corsairdev/corsair
Length of output: 1254
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- Webhook processing: request creation and handler call ---'
sed -n '176,285p' packages/corsair/webhooks/index.ts
rg -n -C 10 'rawBody:|rawBody\?|handler\\(' packages/corsair | head -n 240
printf '%s\n' '--- Core webhook utilities ---'
cat -n packages/corsair/async-core/webhook-utils.ts | sed -n '1,115p'
printf '%s\n' '--- Existing provider verifier implementations ---'
rg -l 'verify[A-Za-z]+WebhookSignature' packages --glob '*.ts' \
| while read -r file; do
echo "--- $file"
rg -n -C 18 'verify[A-Za-z]+WebhookSignature' "$file"
doneRepository: corsairdev/corsair
Length of output: 3511
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
path = Path("packages/corsair/webhooks/index.ts")
text = path.read_text()
for pattern in [
r"const rawRequest = \{.*?\} satisfies RawWebhookRequest;",
r"const request: WebhookRequest.*?;",
r"handler\s*:\s*",
r"rawBody",
r"keyBuilder",
]:
print(f"PATTERN {pattern}")
matches = list(re.finditer(pattern, text, re.S))
for match in matches:
start = max(0, match.start() - 500)
end = min(len(text), match.end() + 900)
print(text[start:end])
print("---")
PYRepository: corsairdev/corsair
Length of output: 4687
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
types = Path("packages/adrapid/webhooks/types.ts").read_text()
example = Path("packages/adrapid/webhooks/example.ts").read_text()
plugin = Path("packages/adrapid/index.ts").read_text()
core = Path("packages/corsair/webhooks/index.ts").read_text()
function = re.search(
r"export function verifyAdrapidWebhookSignature\([\s\S]*?\n\}",
types,
)
assert function, "Adrapid verifier not found"
body = function.group(0)
assert "request" not in body.split("):", 1)[-1].split("{", 1)[-1], \
"Verifier body unexpectedly uses request"
assert "secret" not in body.split("):", 1)[-1].split("{", 1)[-1], \
"Verifier body unexpectedly uses secret"
assert "return { valid: true }" in body
assert "x-adrapid-signature" in plugin
assert "verifyAdrapidWebhookSignature(request, ctx.key)" in example
assert "if (!verification.valid)" in example
assert "logEventFromContext" in example
assert "rawBody: typeof body === 'string' ? body : JSON.stringify(body)" in core
print("A request with any x-adrapid-signature header reaches the handler.")
print("The current verifier returns valid=true without inspecting request or secret.")
print("The handler logs the payload after the verifier returns valid=true.")
print("The webhook pipeline preserves the input string as rawBody when callers provide it.")
PYRepository: corsairdev/corsair
Length of output: 453
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- Adrapid webhook tests and signature-related documentation ---'
rg -n -C 8 \
'Adrapid|x-adrapid-signature|webhookSecret|signature' \
packages/adrapid README.md docs .github 2>/dev/null \
| head -n 240Repository: corsairdev/corsair
Length of output: 13294
Implement Adrapid webhook signature verification before accepting events.
verifyAdrapidWebhookSignature always returns { valid: true }. Since the matcher only checks for x-adrapid-signature, an unauthenticated request can reach logEventFromContext. Use the configured signature scheme with request.rawBody, reject missing or malformed signatures and secrets, and use a timing-safe comparison for HMAC signatures. Add tests for valid, invalid, missing, and malformed signatures.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/adrapid/webhooks/types.ts` around lines 56 - 62, The
verifyAdrapidWebhookSignature function must perform real authentication instead
of always returning valid. Implement the configured signature scheme using
request.rawBody and the provided secret, reject missing or malformed signatures
or secrets, and compare HMAC signatures with a timing-safe method; add tests
covering valid, invalid, missing, and malformed signatures.
What changed
The
youcomplugin was already implemented underpackages/youcom/, but it was missing from the core provider registry.I registered it in
packages/corsair/core/constants.tsby:youcomtoBaseProvidersyoucom: 'You.com'toProviderDisplayNamesyoucomto theAllProviderstypeI also updated
packages/youcom/jest.config.cjswith the required module mappings forcorsair/coreandcorsair/hub.Verification
All checks pass:
pnpm run validate:pluginspnpm typecheckpnpm lintpnpm --filter @corsair-dev/youcom test— 22/22 tests passingSummary by CodeRabbit