mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-16 18:34:18 +00:00
fix: proxy direct APNs HTTP2 sessions (#74905)
Summary: - This PR routes direct APNs HTTP/2 sends through an APNs allowlisted managed-proxy CONNECT wrapper, adds APNs proxy validation/docs/guardrails, and expands regression and live-test coverage. - Reproducibility: yes. source-reproducible: current main `sendApnsRequest()` still uses raw `http2.connect(au ... nly covers HTTP/global-agent/Undici hooks. I did not run a live APNs reproduction in this read-only review. Automerge notes: - PR branch already contained follow-up commit before automerge: test: guard raw HTTP2 APNs connections - PR branch already contained follow-up commit before automerge: test: guard raw HTTP2 with OpenGrep - PR branch already contained follow-up commit before automerge: lint: ban raw HTTP2 imports - PR branch already contained follow-up commit before automerge: fix: use managed proxy state for APNs - PR branch already contained follow-up commit before automerge: test: exercise APNs active proxy state - PR branch already contained follow-up commit before automerge: fix: reject conflicting managed proxy activation Validation: - ClawSweeper review passed for headdab7c86a75. - Required merge gates passed before the squash merge. Prepared head SHA:dab7c86a75Review: https://github.com/openclaw/openclaw/pull/74905#issuecomment-4350181159 Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
This commit is contained in:
@@ -43,6 +43,8 @@ describe("proxy cli runtime", () => {
|
||||
"OPENCLAW_DEBUG_PROXY_CERT_DIR",
|
||||
"OPENCLAW_DEBUG_PROXY_SESSION_ID",
|
||||
"OPENCLAW_DEBUG_PROXY_ENABLED",
|
||||
"FORCE_COLOR",
|
||||
"NO_COLOR",
|
||||
] as const;
|
||||
const savedEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]]));
|
||||
let tempDir = "";
|
||||
@@ -54,6 +56,8 @@ describe("proxy cli runtime", () => {
|
||||
process.env.OPENCLAW_DEBUG_PROXY_CERT_DIR = path.join(tempDir, "certs");
|
||||
delete process.env.OPENCLAW_DEBUG_PROXY_ENABLED;
|
||||
delete process.env.OPENCLAW_DEBUG_PROXY_SESSION_ID;
|
||||
delete process.env.FORCE_COLOR;
|
||||
process.env.NO_COLOR = "1";
|
||||
getRuntimeConfigMock.mockReset();
|
||||
getRuntimeConfigMock.mockReturnValue({
|
||||
proxy: {
|
||||
@@ -109,6 +113,8 @@ describe("proxy cli runtime", () => {
|
||||
proxyUrl: "http://override.example:3128",
|
||||
allowedUrls: ["https://allowed.example/"],
|
||||
deniedUrls: ["http://127.0.0.1/"],
|
||||
apnsReachability: true,
|
||||
apnsAuthority: "https://api.sandbox.push.apple.com",
|
||||
timeoutMs: 1234,
|
||||
});
|
||||
|
||||
@@ -122,6 +128,8 @@ describe("proxy cli runtime", () => {
|
||||
proxyUrlOverride: "http://override.example:3128",
|
||||
allowedUrls: ["https://allowed.example/"],
|
||||
deniedUrls: ["http://127.0.0.1/"],
|
||||
apnsReachability: true,
|
||||
apnsAuthority: "https://api.sandbox.push.apple.com",
|
||||
timeoutMs: 1234,
|
||||
});
|
||||
expect(process.stdout.write).toHaveBeenCalledWith(
|
||||
@@ -307,6 +315,66 @@ describe("proxy cli runtime", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("prints check errors on the same line", async () => {
|
||||
runProxyValidationMock.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
config: {
|
||||
enabled: true,
|
||||
proxyUrl: "http://proxy.example:3128",
|
||||
source: "config",
|
||||
errors: [],
|
||||
},
|
||||
checks: [
|
||||
{
|
||||
kind: "denied",
|
||||
url: "http://127.0.0.1:12345/",
|
||||
ok: true,
|
||||
error: "fetch failed",
|
||||
},
|
||||
],
|
||||
});
|
||||
const { runProxyValidateCommand } = await import("./proxy-cli.runtime.js");
|
||||
|
||||
await runProxyValidateCommand({});
|
||||
|
||||
expect(process.stdout.write).toHaveBeenCalledWith(
|
||||
"Proxy validation passed\n\n" +
|
||||
"Proxy\n" +
|
||||
" Source: config\n" +
|
||||
" URL: http://proxy.example:3128/\n\n" +
|
||||
"Checks\n" +
|
||||
" ✓ denied http://127.0.0.1:12345/ — fetch failed\n",
|
||||
);
|
||||
});
|
||||
|
||||
it("applies the terminal color theme when rich output is enabled", async () => {
|
||||
vi.resetModules();
|
||||
vi.doMock("../terminal/theme.js", () => ({
|
||||
colorize: (rich: boolean, color: (value: string) => string, value: string) =>
|
||||
rich ? color(value) : value,
|
||||
isRich: () => true,
|
||||
theme: {
|
||||
heading: (value: string) => `<heading>${value}</heading>`,
|
||||
success: (value: string) => `<success>${value}</success>`,
|
||||
error: (value: string) => `<error>${value}</error>`,
|
||||
muted: (value: string) => `<muted>${value}</muted>`,
|
||||
warn: (value: string) => `<warn>${value}</warn>`,
|
||||
},
|
||||
}));
|
||||
try {
|
||||
const { runProxyValidateCommand } = await import("./proxy-cli.runtime.js");
|
||||
|
||||
await runProxyValidateCommand({});
|
||||
|
||||
const output = String(vi.mocked(process.stdout.write).mock.calls[0]?.[0] ?? "");
|
||||
expect(output).toContain("<success>Proxy validation passed</success>");
|
||||
expect(output).toContain("<heading>Checks</heading>");
|
||||
expect(output).toContain("<success>✓</success>");
|
||||
} finally {
|
||||
vi.doUnmock("../terminal/theme.js");
|
||||
}
|
||||
});
|
||||
|
||||
it("prints actionable check failure output", async () => {
|
||||
runProxyValidationMock.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
@@ -343,8 +411,7 @@ describe("proxy cli runtime", () => {
|
||||
" URL: http://proxy.example:3128/\n\n" +
|
||||
"Checks\n" +
|
||||
" ✓ allowed http://target.example/allowed HTTP 200\n" +
|
||||
" ✗ denied http://target.example/allowed HTTP 200\n" +
|
||||
" Denied destination was reachable through the proxy\n\n" +
|
||||
" ✗ denied http://target.example/allowed HTTP 200 — Denied destination was reachable through the proxy\n\n" +
|
||||
"Next steps\n" +
|
||||
" Update the proxy ACL so denied destinations are blocked, or pass the expected --denied-url values.\n",
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user