fix(crabbox): require broker auth for aws proof

This commit is contained in:
Vincent Koc
2026-05-27 02:05:38 +01:00
parent d353dc128f
commit 7c432d2bd8
3 changed files with 101 additions and 3 deletions
+3 -3
View File
@@ -630,7 +630,7 @@ Install/auth for owned Crabbox if needed:
```sh
brew install openclaw/tap/crabbox
crabbox login --url https://crabbox.openclaw.ai --provider aws
crabbox login --provider aws
```
New users should self-resolve broker auth before anyone asks for AWS keys:
@@ -641,7 +641,7 @@ crabbox doctor
crabbox whoami
```
- If broker auth is missing, run `crabbox login --url https://crabbox.openclaw.ai --provider aws`.
- If broker auth is missing, run `crabbox login --provider aws`.
- If the CLI asks for `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, or AWS
profile setup during normal OpenClaw validation, assume the agent selected
the wrong path. Use brokered `crabbox login` or an existing brokered lease
@@ -649,7 +649,7 @@ crabbox whoami
- Ask for AWS keys only for explicit direct-provider/account administration,
not for normal brokered OpenClaw proof.
- Trusted automation may still use
`printf '%s' "$CRABBOX_COORDINATOR_TOKEN" | crabbox login --url https://crabbox.openclaw.ai --provider aws --token-stdin`.
`printf '%s' "$CRABBOX_COORDINATOR_TOKEN" | crabbox login --provider aws --token-stdin`.
macOS config lives at:
+45
View File
@@ -175,6 +175,7 @@ function checkedOutput(command, commandArgs) {
return {
status: result.status ?? 1,
text: `${result.stdout ?? ""}${result.stderr ?? ""}`.trim(),
stdout: (result.stdout ?? "").trim(),
};
}
@@ -392,6 +393,48 @@ function selectedProvider(commandArgs) {
return commandProvider(commandArgs) || configuredProvider();
}
function shouldRequireBrokeredAws(commandArgs, providerName) {
if (process.env.OPENCLAW_CRABBOX_ALLOW_DIRECT_AWS === "1") {
return false;
}
const canonicalProvider = providerAliases.get(providerName) ?? providerName;
if (canonicalProvider !== "aws") {
return false;
}
if (commandArgs[0] === "run" || commandArgs[0] === "warmup") {
return true;
}
return commandArgs[0] === "actions" && commandArgs[1] === "hydrate";
}
function brokerAuthConfigured() {
const config = checkedOutput(binary, ["config", "show", "--json"]);
if (config.status !== 0) {
return false;
}
let parsed;
try {
parsed = JSON.parse(config.stdout || config.text);
} catch {
return false;
}
return Boolean(parsed?.coordinator && parsed?.brokerAuth === "configured");
}
function enforceBrokeredAws(commandArgs, providerName) {
if (!shouldRequireBrokeredAws(commandArgs, providerName) || brokerAuthConfigured()) {
return;
}
console.error(
[
"[crabbox] provider=aws requires a configured Crabbox broker for OpenClaw proof.",
"[crabbox] run `crabbox login --provider aws`, then retry.",
"[crabbox] for intentional direct AWS provider debugging, set OPENCLAW_CRABBOX_ALLOW_DIRECT_AWS=1.",
].join("\n"),
);
process.exit(2);
}
function optionValue(commandArgs, name) {
commandArgs = crabboxOptionArgs(commandArgs);
for (let index = 0; index < commandArgs.length; index += 1) {
@@ -1722,6 +1765,8 @@ if (provider && !isProviderAdvertised(provider, providers)) {
process.exit(2);
}
enforceBrokeredAws(normalizedArgs, provider);
if (provider === "blacksmith-testbox") {
const envProvider = process.env.CRABBOX_PROVIDER?.trim();
const source = commandProviderValue
+53
View File
@@ -28,6 +28,15 @@ function writeFakeCrabbox(binDir: string, helpText: string): string {
` process.stdout.write(${JSON.stringify(helpText)});`,
" process.exit(0);",
"}",
'if (args[0] === "config" && args[1] === "show" && args.includes("--json")) {',
" const status = Number.parseInt(process.env.OPENCLAW_FAKE_CRABBOX_CONFIG_STATUS || '0', 10);",
" if (status !== 0) {",
" process.stderr.write('config unavailable\\n');",
" process.exit(status);",
" }",
" process.stdout.write(process.env.OPENCLAW_FAKE_CRABBOX_CONFIG_JSON || '{\"coordinator\":\"configured-broker\",\"brokerAuth\":\"configured\"}');",
" process.exit(0);",
"}",
"const scriptIndex = args.findIndex((arg) => arg === '--script' || arg === '-script');",
"const scriptPath = scriptIndex >= 0 ? args[scriptIndex + 1] : '';",
"const scriptContent = scriptPath ? require('node:fs').readFileSync(scriptPath, 'utf8') : '';",
@@ -75,6 +84,9 @@ function runWrapper(
helpText: string,
args: string[],
options: {
configJson?: Record<string, unknown>;
configStatus?: number;
env?: Record<string, string>;
extraPathEntries?: string[];
gitResponses?: Record<string, { status?: number; stdout?: string; stderr?: string }>;
input?: string;
@@ -92,6 +104,13 @@ function runWrapper(
.filter(Boolean)
.join(path.delimiter),
OPENCLAW_CRABBOX_WRAPPER_IGNORE_REPO_BINARY: "1",
...(options.configJson
? { OPENCLAW_FAKE_CRABBOX_CONFIG_JSON: JSON.stringify(options.configJson) }
: {}),
...(options.configStatus
? { OPENCLAW_FAKE_CRABBOX_CONFIG_STATUS: String(options.configStatus) }
: {}),
...(options.env ?? {}),
...(options.gitResponses
? { OPENCLAW_FAKE_GIT_RESPONSES: JSON.stringify(options.gitResponses) }
: {}),
@@ -189,6 +208,40 @@ describe("scripts/crabbox-wrapper", () => {
]);
});
it("fails closed for AWS proof when broker auth is missing", () => {
const result = runWrapper(
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
["run", "--provider", "aws", "--", "echo ok"],
{ configJson: { coordinator: "", brokerAuth: "missing" } },
);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("provider=aws requires a configured Crabbox broker");
expect(result.stderr).toContain("crabbox login --provider aws");
expect(result.stderr).not.toContain("crabbox.openclaw.ai");
});
it("allows explicit direct AWS debugging without broker auth", () => {
const result = runWrapper(
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
["run", "--provider", "aws", "--", "echo ok"],
{
configJson: { coordinator: "", brokerAuth: "missing" },
env: { OPENCLAW_CRABBOX_ALLOW_DIRECT_AWS: "1" },
},
);
expect(result.status).toBe(0);
expect(parseFakeCrabboxOutput(result).args).toEqual([
"run",
"--provider",
"aws",
"--",
"echo ok",
]);
});
it("defaults AWS macOS warmups to on-demand capacity", () => {
const result = runWrapper(
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",