mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-04 22:15:07 +03:00
Compare commits
1 Commits
fix/releas
...
fix/releas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9b70d3c6a |
@@ -106,16 +106,19 @@ function parsePatterns(name) {
|
||||
const LOCAL_ONLY_PREFIXES = parsePrefixes("LOCAL_ONLY_API_PREFIXES");
|
||||
const LOCAL_ONLY_PATTERNS = parsePatterns("LOCAL_ONLY_API_PATTERNS");
|
||||
const ALWAYS_PROTECTED_PATHS = parsePrefixes("ALWAYS_PROTECTED_API_PATHS");
|
||||
const ALWAYS_PROTECTED_PATTERNS = parsePatterns("ALWAYS_PROTECTED_API_PATTERNS");
|
||||
|
||||
if (
|
||||
LOCAL_ONLY_PREFIXES.length === 0 ||
|
||||
LOCAL_ONLY_PATTERNS.length === 0 ||
|
||||
ALWAYS_PROTECTED_PATHS.length === 0
|
||||
ALWAYS_PROTECTED_PATHS.length === 0 ||
|
||||
ALWAYS_PROTECTED_PATTERNS.length === 0
|
||||
) {
|
||||
console.error(
|
||||
`[openapi-security-tiers] FAIL — could not parse routeGuard.ts constants ` +
|
||||
`(prefixes=${LOCAL_ONLY_PREFIXES.length}, patterns=${LOCAL_ONLY_PATTERNS.length}, ` +
|
||||
`alwaysProtected=${ALWAYS_PROTECTED_PATHS.length})`
|
||||
`alwaysProtected=${ALWAYS_PROTECTED_PATHS.length}, ` +
|
||||
`alwaysProtectedPatterns=${ALWAYS_PROTECTED_PATTERNS.length})`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -135,6 +138,19 @@ function coveredByLocalOnly(pathStr) {
|
||||
return matchesPrefix(concrete) || LOCAL_ONLY_PATTERNS.some((re) => re.test(concrete));
|
||||
}
|
||||
|
||||
// Same two-array shape as the LOCAL_ONLY tier: routeGuard protects a path when
|
||||
// EITHER list matches (`isAlwaysProtectedPath` ORs them), so reading only the
|
||||
// prefix array reports every regex-covered route as an annotation mismatch.
|
||||
// That is what happened to the four `{claude,codex}-auth/{export,apply-local}`
|
||||
// routes, which ALWAYS_PROTECTED_API_PATTERNS has always covered.
|
||||
function coveredByAlwaysProtected(pathStr) {
|
||||
const concrete = concretize(pathStr);
|
||||
return (
|
||||
ALWAYS_PROTECTED_PATHS.some((p) => concrete === p || concrete.startsWith(`${p}/`)) ||
|
||||
ALWAYS_PROTECTED_PATTERNS.some((re) => re.test(concrete))
|
||||
);
|
||||
}
|
||||
|
||||
const raw = yaml.load(fs.readFileSync(OPENAPI_PATH, "utf-8"));
|
||||
const paths = raw.paths || {};
|
||||
const errors = [];
|
||||
@@ -151,16 +167,11 @@ for (const [pathStr, methods] of Object.entries(paths)) {
|
||||
);
|
||||
}
|
||||
|
||||
if (spec["x-always-protected"] === true) {
|
||||
const matchesPath = ALWAYS_PROTECTED_PATHS.some(
|
||||
(p) => pathStr === p || pathStr.startsWith(`${p}/`)
|
||||
if (spec["x-always-protected"] === true && !coveredByAlwaysProtected(pathStr)) {
|
||||
errors.push(
|
||||
`${method.toUpperCase()} ${pathStr}: has x-always-protected but is NOT covered by ` +
|
||||
`ALWAYS_PROTECTED_API_PATHS or ALWAYS_PROTECTED_API_PATTERNS`
|
||||
);
|
||||
if (!matchesPath) {
|
||||
errors.push(
|
||||
`${method.toUpperCase()} ${pathStr}: has x-always-protected but is NOT in ` +
|
||||
`ALWAYS_PROTECTED_API_PATHS [${ALWAYS_PROTECTED_PATHS.join(", ")}]`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
tests/unit/openapi-security-tiers-gate.test.ts
Normal file
38
tests/unit/openapi-security-tiers-gate.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||
const GATE = join(ROOT, "scripts", "check", "check-openapi-security-tiers.mjs");
|
||||
|
||||
function runGate(): { code: number; out: string } {
|
||||
try {
|
||||
const out = execFileSync(process.execPath, [GATE], {
|
||||
cwd: ROOT,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
return { code: 0, out };
|
||||
} catch (err) {
|
||||
const e = err as { status?: number; stdout?: string; stderr?: string };
|
||||
return { code: e.status ?? 1, out: `${e.stdout ?? ""}${e.stderr ?? ""}` };
|
||||
}
|
||||
}
|
||||
|
||||
// routeGuard protects a path when EITHER list matches — `isAlwaysProtectedPath`
|
||||
// ORs ALWAYS_PROTECTED_API_PATHS with ALWAYS_PROTECTED_API_PATTERNS. The gate
|
||||
// used to read only the prefix array, so every regex-covered route was reported
|
||||
// as an annotation mismatch: the four `{claude,codex}-auth/{export,apply-local}`
|
||||
// routes turned release/v3.8.51 red while being correctly protected at runtime.
|
||||
// Same defect class the LOCAL_ONLY arm already had (#12350).
|
||||
test("openapi-security-tiers accepts routes covered only by ALWAYS_PROTECTED_API_PATTERNS", () => {
|
||||
const { code, out } = runGate();
|
||||
|
||||
assert.ok(
|
||||
!/has x-always-protected but is NOT/.test(out),
|
||||
`gate reported an always-protected route as uncovered:\n${out}`
|
||||
);
|
||||
assert.equal(code, 0, `gate must pass on a clean tree, got exit ${code}:\n${out}`);
|
||||
});
|
||||
Reference in New Issue
Block a user