fix(slack): align interaction auth with allowlists (#66028)

* fix(slack): align interaction auth with allowlists

* fix(slack): address review followups

* fix(slack): preserve explicit owners with wildcard

* chore: append Claude comments resolution worklog

* fix(slack): harden interaction auth with default-deny, mandatory actor binding, and channel type validation

- Add interactiveEvent flag to authorizeSlackSystemEventSender for stricter
  interactive control authorization
- Default-deny when no allowFrom or channel users are configured for
  interactive events (block actions, modals)
- Require expectedSenderId for all interactive event types; block actions
  pass Slack-verified userId, modals pass metadata-embedded userId
- Reject ambiguous channel types for interactive events to prevent DM
  authorization bypass via channel-type fallback
- Add comprehensive test coverage for all new behaviors

* fix(slack): scope interactive owner/allowFrom enforcement to interactive paths only

* fix(slack): preserve no-channel interactive default

* Update context-engine-maintenance test

* chore: remove USER.md worklog artifact

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* changelog: note Slack interactive auth allowlist alignment (#66028)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Devin Robison <drobison@nvidia.com>
This commit is contained in:
Agustin Rivera
2026-04-13 19:38:11 -07:00
committed by GitHub
parent 49d99c7500
commit 1c35795fce
7 changed files with 770 additions and 39 deletions

View File

@@ -171,14 +171,16 @@ describe("buildContextEngineMaintenanceRuntimeContext", () => {
});
await Promise.resolve();
rewriteTranscriptEntriesInSessionFileMock.mockImplementationOnce(async (_params?: unknown) => {
events.push("rewrite");
return {
changed: true,
bytesFreed: 123,
rewrittenEntries: 2,
};
});
rewriteTranscriptEntriesInSessionFileMock.mockImplementationOnce(
async (_params?: unknown) => {
events.push("rewrite");
return {
changed: true,
bytesFreed: 123,
rewrittenEntries: 2,
};
},
);
const runtimeContext = buildContextEngineMaintenanceRuntimeContext({
sessionId: "session-rewrite-handoff",
@@ -836,19 +838,20 @@ describe("runContextEngineMaintenance", () => {
allowRewrite = resolve;
});
events.push("maintenance-before-rewrite");
await (params as { runtimeContext?: ContextEngineRuntimeContext }).runtimeContext
?.rewriteTranscriptEntries?.({
replacements: [
{
entryId: "entry-1",
message: castAgentMessage({
role: "assistant",
content: [{ type: "text", text: "done" }],
timestamp: 2,
}),
},
],
});
await (
params as { runtimeContext?: ContextEngineRuntimeContext }
).runtimeContext?.rewriteTranscriptEntries?.({
replacements: [
{
entryId: "entry-1",
message: castAgentMessage({
role: "assistant",
content: [{ type: "text", text: "done" }],
timestamp: 2,
}),
},
],
});
events.push("maintenance-after-rewrite");
return {
changed: false,
@@ -857,14 +860,16 @@ describe("runContextEngineMaintenance", () => {
};
});
rewriteTranscriptEntriesInSessionFileMock.mockImplementationOnce(async (_params?: unknown) => {
events.push("rewrite");
return {
changed: true,
bytesFreed: 123,
rewrittenEntries: 2,
};
});
rewriteTranscriptEntriesInSessionFileMock.mockImplementationOnce(
async (_params?: unknown) => {
events.push("rewrite");
return {
changed: true,
bytesFreed: 123,
rewrittenEntries: 2,
};
},
);
const backgroundEngine = {
info: {