Commit 7ef21dc
fix(cli): linear setup must not clobber a workspace's webhook signing secret (aws-samples#611) (aws-samples#612)
* fix(cli): linear setup must not clobber a workspace's webhook secret (aws-samples#611)
Re-running `bgagent linear setup <slug>` on an already-installed workspace
overwrote that workspace's per-workspace webhook signing secret with the
STACK-WIDE fallback — which belongs to whichever workspace installed first. In a
multi-workspace deployment this silently breaks HMAC signature verification for
every workspace after the first: the receiver rejects Linear's deliveries with
401 {"error":"Invalid signature"} (live-hit on demo-abca after a re-auth).
Root cause (linear.ts setup action): the mirror was guarded only on
`stackWideAlreadyConfigured`, and the OAuth bundle is re-upserted fresh before
that block, so the existing per-workspace `webhook_signing_secret` was already
dropped and then replaced with the wrong (stack-wide) value.
Fix:
- New pure helper `resolveWebhookSecretAction(existingPerWorkspaceSecret,
stackWideConfigured)` → preserve | mirror-stackwide | prompt. An existing valid
per-workspace secret always wins (rotation stays `update-webhook-secret`'s job);
else mirror the stack-wide secret (with a warning to verify for an additional
workspace); else prompt. 5 unit tests incl. the multi-workspace re-run case.
- `setup` reads the existing per-workspace secret BEFORE the OAuth overwrite and
preserves it; the mirror path now warns that an additional workspace's Linear
secret differs.
cli build green (610 tests, compile + eslint clean).
* fix(cli): address aws-samples#612 review — fail-closed webhook pre-read + tests + nits
B1 (fail-open re-armed the clobber): the pre-read catch swallowed EVERY
GetSecretValue error and treated it as 'first install — nothing to preserve'.
That only holds for ResourceNotFoundException; for AccessDenied / KMSAccessDenied
/ Throttling / network / a corrupt bundle — all on a secret that EXISTS —
existingWebhookSecret stayed undefined → resolveWebhookSecretAction mirrors the
stack-wide (workspace #1's) secret over this workspace's real one, re-arming the
aws-samples#611 401 clobber silently behind a green 'Setup complete'. Extracted the pre-read
into readExistingWebhookSecret() which FAILS CLOSED: only ResourceNotFound → 'none
to preserve'; a corrupt bundle (JSON.parse) or any other SM error propagates, and
setup re-throws an actionable CliError with an IAM/KMS hint (mirroring the sibling
isWebhookSecretConfigured).
B2 (fix was untested end-to-end): the 5 prior tests only exercised the pure
resolveWebhookSecretAction. Added 7 tests for readExistingWebhookSecret covering
the load-bearing seam — preserve-not-clobber, ResourceNotFound→undefined,
malformed/absent secret, and the fail-closed THROW on AccessDenied / KMS /
Throttling / corrupt JSON (the exact B1 path).
N1 (stale comment): the Step-6 header still described the REMOVED behavior ('the
stored value is this workspace's secret — lift it in'). Rewrote it to describe the
preserve → mirror-stackwide → prompt decision so a maintainer can't revert the fix.
N2 (double-write + failure window): fold the preserved secret into the INITIAL
bundle so the OAuth-secret write lands it in ONE PutSecretValue; the
preserve path skips the later re-write (guarded by !== stored.webhook_signing_secret).
Closes the window where a throw between the two writes left the bundle persisted
without the secret (401 until update-webhook-secret).
Full cli build green (616 tests).
* test(cli): end-to-end setup regression for the second-workspace webhook-secret clobber (aws-samples#612 review B2 / aws-samples#611 AC#3)
Scott's re-review approved with one should-fix follow-up (B2): the safety seam
(readExistingWebhookSecret fail-closed) and the pure decision
(resolveWebhookSecretAction) were unit-tested, but nothing drove the `setup`
ACTION end-to-end to prove issue aws-samples#611's AC#3 against the final PutSecretValue
payload — linear.ts's secretAction branch + mirror-back guard were uncovered.
New test drives `bgagent linear setup demo --no-browser` through parseAsync with
the OAuth flow mocked (awaitOauthCallback echoes the wizard's generated state,
scraped from the printed auth URL; exchangeAuthorizationCode + fetch +
SecretsManager + CFN + DynamoDB stubbed), for the second-workspace re-run:
- prior per-workspace bundle carries lin_wh_thisWorkspace,
- stack-wide holds a DIFFERENT lin_wh_otherWorkspace (the aws-samples#611 clobber source).
Asserts the final per-workspace PutSecretValue payload keeps lin_wh_thisWorkspace
(preserve, folded into the initial bundle — never the stack-wide other), that
the stack-wide ARN is never overwritten by setup, and that the registry row is
still written (setup ran to completion). A refactor that re-broke the wiring now
fails a test, not just inspection.
Partial-mocks linear-oauth (keeps the real readExistingWebhookSecret /
resolveWebhookSecretAction under test; stubs only exchangeAuthorizationCode).
cli gate green: 621 tests, compile + eslint clean.
---------
Co-authored-by: Scott Schreckengaust <scottschreckengaust@users.noreply.github.com>1 parent 6b40f5f commit 7ef21dc
4 files changed
Lines changed: 512 additions & 13 deletions
File tree
- cli
- src
- commands
- test
- commands
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| 46 | + | |
| 47 | + | |
46 | 48 | | |
47 | 49 | | |
48 | 50 | | |
| |||
601 | 603 | | |
602 | 604 | | |
603 | 605 | | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
604 | 642 | | |
605 | 643 | | |
606 | 644 | | |
| |||
615 | 653 | | |
616 | 654 | | |
617 | 655 | | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
618 | 664 | | |
619 | 665 | | |
620 | 666 | | |
| |||
676 | 722 | | |
677 | 723 | | |
678 | 724 | | |
679 | | - | |
680 | | - | |
681 | | - | |
682 | | - | |
683 | | - | |
684 | | - | |
685 | | - | |
686 | | - | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
687 | 740 | | |
688 | 741 | | |
689 | 742 | | |
690 | | - | |
691 | | - | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
692 | 759 | | |
693 | 760 | | |
694 | 761 | | |
| |||
725 | 792 | | |
726 | 793 | | |
727 | 794 | | |
728 | | - | |
729 | | - | |
730 | | - | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
731 | 800 | | |
732 | 801 | | |
733 | 802 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
293 | 293 | | |
294 | 294 | | |
295 | 295 | | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
0 commit comments