(config: OpenClawConfig)
| 732 | } |
| 733 | |
| 734 | function detectAccessControlMisconfiguration(config: OpenClawConfig): string | null { |
| 735 | const root = getRecord(config); |
| 736 | if (!root) { |
| 737 | return null; |
| 738 | } |
| 739 | |
| 740 | const session = getRecord(root.session); |
| 741 | const dmScope = session ? getString(session.dmScope)?.toLowerCase() : null; |
| 742 | const allowedDmScopes = new Set(["per-channel-peer", "per-peer", "private"]); |
| 743 | const broadDmScope = !dmScope || !allowedDmScopes.has(dmScope); |
| 744 | |
| 745 | const gateway = getRecord(root.gateway); |
| 746 | const nodes = gateway ? getRecord(gateway.nodes) : null; |
| 747 | const denyCommands = nodes ? getStringArray(nodes.denyCommands) : null; |
| 748 | const denyListMissing = !denyCommands; |
| 749 | |
| 750 | const denySet = new Set((denyCommands ?? []).map((command) => command.toLowerCase())); |
| 751 | const requiredSensitiveCommands = [ |
| 752 | "camera.snap", |
| 753 | "camera.clip", |
| 754 | "screen.record", |
| 755 | "contacts.add", |
| 756 | "calendar.add", |
| 757 | "reminders.add", |
| 758 | "sms.send", |
| 759 | ]; |
| 760 | |
| 761 | const missingCommands = requiredSensitiveCommands.filter((command) => !denySet.has(command)); |
| 762 | const weakDenyList = denyListMissing || missingCommands.length > 0; |
| 763 | |
| 764 | if (broadDmScope && weakDenyList) { |
| 765 | if (denyListMissing) { |
| 766 | return [ |
| 767 | "Category: Access Control", |
| 768 | `Reason: session.dmScope is '${dmScope ?? "(missing)"}' and gateway.nodes.denyCommands is missing.`, |
| 769 | ].join("\n"); |
| 770 | } |
| 771 | |
| 772 | return [ |
| 773 | "Category: Access Control", |
| 774 | `Reason: session.dmScope is '${dmScope ?? "(missing)"}' and deny list misses: ${missingCommands.join(", ")}.`, |
| 775 | ].join("\n"); |
| 776 | } |
| 777 | |
| 778 | return null; |
| 779 | } |
| 780 | |
| 781 | |
| 782 | async function scanSkillDirectory( |
no test coverage detected