@andypai/agent-kanban 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andypai/agent-kanban",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Agent-friendly kanban board CLI. Manage tasks via bash commands, parse structured JSON output.",
|
|
5
5
|
"homepage": "https://github.com/abpai/agent-kanban#readme",
|
|
6
6
|
"repository": {
|
|
@@ -690,8 +690,8 @@ describe('Linear webhook', () => {
|
|
|
690
690
|
})
|
|
691
691
|
})
|
|
692
692
|
|
|
693
|
-
describe('webhook
|
|
694
|
-
test('Jira
|
|
693
|
+
describe('webhook open dev mode when secret is unset', () => {
|
|
694
|
+
test('Jira accepts webhooks when JIRA_WEBHOOK_SECRET is unset', async () => {
|
|
695
695
|
const prev = process.env['JIRA_WEBHOOK_SECRET']
|
|
696
696
|
delete process.env['JIRA_WEBHOOK_SECRET']
|
|
697
697
|
try {
|
|
@@ -703,17 +703,33 @@ describe('webhook fail-closed when secret is unset', () => {
|
|
|
703
703
|
apiToken: jiraConfig.apiToken,
|
|
704
704
|
})
|
|
705
705
|
const provider = new JiraProvider(db, jiraConfig, client)
|
|
706
|
-
const body = JSON.stringify({
|
|
706
|
+
const body = JSON.stringify({
|
|
707
|
+
webhookEvent: 'jira:issue_created',
|
|
708
|
+
issue: {
|
|
709
|
+
id: '100',
|
|
710
|
+
key: 'ENG-100',
|
|
711
|
+
fields: {
|
|
712
|
+
summary: 'New issue',
|
|
713
|
+
status: { id: '1', name: 'To Do' },
|
|
714
|
+
issuetype: { id: '10000', name: 'Task' },
|
|
715
|
+
assignee: null,
|
|
716
|
+
labels: [],
|
|
717
|
+
comment: { total: 0 },
|
|
718
|
+
created: '2025-02-01T00:00:00.000Z',
|
|
719
|
+
updated: '2025-02-01T00:00:00.000Z',
|
|
720
|
+
project: { id: '1', key: 'ENG' },
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
})
|
|
707
724
|
const result = await provider.handleWebhook({ headers: {}, rawBody: body })
|
|
708
|
-
expect(result.
|
|
709
|
-
expect(result.unauthorized).toBe(true)
|
|
725
|
+
expect(result.unauthorized).not.toBe(true)
|
|
710
726
|
} finally {
|
|
711
727
|
if (prev === undefined) delete process.env['JIRA_WEBHOOK_SECRET']
|
|
712
728
|
else process.env['JIRA_WEBHOOK_SECRET'] = prev
|
|
713
729
|
}
|
|
714
730
|
})
|
|
715
731
|
|
|
716
|
-
test('Linear
|
|
732
|
+
test('Linear accepts webhooks when LINEAR_WEBHOOK_SECRET is unset', async () => {
|
|
717
733
|
const prev = process.env['LINEAR_WEBHOOK_SECRET']
|
|
718
734
|
delete process.env['LINEAR_WEBHOOK_SECRET']
|
|
719
735
|
try {
|
|
@@ -721,9 +737,9 @@ describe('webhook fail-closed when secret is unset', () => {
|
|
|
721
737
|
seedLinear(db)
|
|
722
738
|
const provider = new LinearProvider(db, 'tid', 'key')
|
|
723
739
|
const body = JSON.stringify({ action: 'create', type: 'Issue', data: { id: 'i1' } })
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
expect(result
|
|
740
|
+
// Auth passes; downstream may fail for other reasons (e.g. API credentials)
|
|
741
|
+
const result = await provider.handleWebhook({ headers: {}, rawBody: body }).catch(() => null)
|
|
742
|
+
expect(result?.unauthorized).not.toBe(true)
|
|
727
743
|
} finally {
|
|
728
744
|
if (prev === undefined) delete process.env['LINEAR_WEBHOOK_SECRET']
|
|
729
745
|
else process.env['LINEAR_WEBHOOK_SECRET'] = prev
|
|
@@ -855,6 +855,11 @@ export class JiraProviderCore implements KanbanProvider {
|
|
|
855
855
|
// Shared webhook dispatch. Postgres wraps this with webhook-event auditing;
|
|
856
856
|
// SQLite calls it directly via the default handleWebhook above.
|
|
857
857
|
protected async handleWebhookCore(payload: WebhookRequest): Promise<WebhookResult> {
|
|
858
|
+
if (!process.env['JIRA_WEBHOOK_SECRET']) {
|
|
859
|
+
console.warn(
|
|
860
|
+
'[jira] JIRA_WEBHOOK_SECRET is not set — accepting webhook without signature verification (open dev mode)',
|
|
861
|
+
)
|
|
862
|
+
}
|
|
858
863
|
const auth = authorizeWebhook({
|
|
859
864
|
secret: process.env['JIRA_WEBHOOK_SECRET'],
|
|
860
865
|
rawBody: payload.rawBody,
|
|
@@ -579,6 +579,11 @@ export class LinearProviderCore implements KanbanProvider {
|
|
|
579
579
|
// Shared webhook dispatch. Postgres wraps this with webhook-event auditing;
|
|
580
580
|
// SQLite calls it directly via the default handleWebhook above.
|
|
581
581
|
protected async handleWebhookCore(payload: WebhookRequest): Promise<WebhookResult> {
|
|
582
|
+
if (!process.env['LINEAR_WEBHOOK_SECRET']) {
|
|
583
|
+
console.warn(
|
|
584
|
+
'[linear] LINEAR_WEBHOOK_SECRET is not set — accepting webhook without signature verification (open dev mode)',
|
|
585
|
+
)
|
|
586
|
+
}
|
|
582
587
|
const auth = authorizeWebhook({
|
|
583
588
|
secret: process.env['LINEAR_WEBHOOK_SECRET'],
|
|
584
589
|
rawBody: payload.rawBody,
|
package/src/webhooks.ts
CHANGED
|
@@ -13,10 +13,9 @@ export interface WebhookResult {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* delivery therefore requires the provider secret to be configured.
|
|
16
|
+
* Webhook authorization. When secret is configured, verifies the HMAC signature
|
|
17
|
+
* and rejects on mismatch. When secret is unset, accepts all payloads (open dev
|
|
18
|
+
* mode — suitable when the provider webhook is not configured with a signing secret).
|
|
20
19
|
*/
|
|
21
20
|
export function authorizeWebhook(opts: {
|
|
22
21
|
secret: string | undefined
|
|
@@ -25,13 +24,7 @@ export function authorizeWebhook(opts: {
|
|
|
25
24
|
verify: (secret: string, rawBody: string, signature: string | undefined | null) => boolean
|
|
26
25
|
}): WebhookResult | null {
|
|
27
26
|
const { secret, rawBody, signature, verify } = opts
|
|
28
|
-
if (!secret)
|
|
29
|
-
return {
|
|
30
|
-
handled: false,
|
|
31
|
-
unauthorized: true,
|
|
32
|
-
message: 'Webhook secret not configured; rejecting unauthenticated webhook',
|
|
33
|
-
}
|
|
34
|
-
}
|
|
27
|
+
if (!secret) return null
|
|
35
28
|
if (!verify(secret, rawBody, signature)) {
|
|
36
29
|
return { handled: false, unauthorized: true, message: 'Invalid signature' }
|
|
37
30
|
}
|