Skip to content

Commit

Permalink
fix: Guard against incorrectly written local rules (#2191)
Browse files Browse the repository at this point in the history
This guards against crashing when a local rule doesn't return a rule result. We need to guard against this in case a user writes a custom rule that doesn't typecheck correctly.
  • Loading branch information
blaine-arcjet authored Nov 8, 2024
1 parent c509b59 commit 0885ccf
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 0 deletions.
12 changes: 12 additions & 0 deletions arcjet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1433,6 +1433,18 @@ export default function arcjet<
localRule.validate(context, details);
results[idx] = await localRule.protect(context, details);

// If a rule didn't return a rule result, we need to stub it to avoid
// crashing. This should only happen if a user writes a custom local
// rule incorrectly.
if (typeof results[idx] === "undefined") {
results[idx] = new ArcjetRuleResult({
ttl: 0,
state: "RUN",
conclusion: "ERROR",
reason: new ArcjetErrorReason("rule result missing"),
});
}

log.debug(
{
id: results[idx].ruleId,
Expand Down
52 changes: 52 additions & 0 deletions arcjet/test/index.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2304,6 +2304,16 @@ describe("SDK", () => {
),
};
}
function testRuleLocalIncorrect(): ArcjetLocalRule {
return {
mode: ArcjetMode.LIVE,
type: "TEST_RULE_LOCAL_INCORRECT",
priority: 1,
validate: jest.fn(),
// @ts-expect-error
protect: jest.fn(async () => undefined),
};
}

function testRuleRemote(): ArcjetRule {
return {
Expand Down Expand Up @@ -2728,6 +2738,48 @@ describe("SDK", () => {
expect(denied.protect).toHaveBeenCalledTimes(1);
});

test("does not crash if a local rule does not return a result", async () => {
const client = {
decide: jest.fn(async () => {
return new ArcjetAllowDecision({
ttl: 0,
reason: new ArcjetTestReason(),
results: [],
});
}),
report: jest.fn(),
};

const request = {
ip: "172.100.1.1",
method: "GET",
protocol: "http",
host: "example.com",
path: "/",
headers: new Headers([["User-Agent", "curl/8.1.2"]]),
"extra-test": "extra-test-value",
};
const rule = testRuleLocalIncorrect();

const aj = arcjet({
key: "test-key",
rules: [[rule]],
client,
log,
});

const context = {
getBody: () => Promise.resolve(undefined),
};

const decision = await aj.protect(context, request);
// ALLOW because the remote rule was called and it returned ALLOW
expect(decision.conclusion).toEqual("ALLOW");

expect(rule.validate).toHaveBeenCalledTimes(1);
expect(rule.protect).toHaveBeenCalledTimes(1);
});

test("returns an ERROR decision if fingerprint cannot be generated", async () => {
const client = {
decide: jest.fn(async () => {
Expand Down

0 comments on commit 0885ccf

Please sign in to comment.