Fix CSP and hook ancestor discovery

Recognize Next.js 16 proxy files when detecting runtime CSP and mirror harness ancestor lookup when locating active hook manifests for nested projects.

AI assistance disclosure: Implemented and verified with Codex under maintainer direction.
This commit is contained in:
Paul Bakaus
2026-09-02 09:13:53 -07:00
parent a264199177
commit be43532192
8 changed files with 109 additions and 6 deletions
+34 -2
View File
@@ -1313,6 +1313,39 @@ function hookEnabledAt(root) {
const STOP_REVIEW_PROVIDERS = new Set(['claude-code', 'codex', 'agents', 'grok']);
// Harness project settings are discovered by walking up from the active
// working directory. Context resolution can intentionally select a nested
// product root even when the harness project (and its hook manifest) lives at
// the enclosing git root, so checking only projectRoot/repoRoot produces a
// false MANUAL_DETECTOR_REQUIRED directive. Mirror the ancestor lookup through
// the nearest git boundary, while retaining exact resolved roots for explicit
// targets outside the invoking directory.
function hookManifestSearchRoots(ctx) {
const roots = [];
const seen = new Set();
const add = (root) => {
if (!root) return;
const resolved = path.resolve(root);
if (seen.has(resolved)) return;
seen.add(resolved);
roots.push(resolved);
};
let current = path.resolve(process.cwd());
const home = path.resolve(os.homedir());
while (true) {
add(current);
if (current === home || hasGitBoundary(current)) break;
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
}
add(ctx.projectRoot);
add(ctx.repoRoot);
return roots;
}
function automaticHookMode(ctx) {
if (ctx.platform === 'ios' || ctx.platform === 'android' || ctx.platform === 'adaptive') {
return 'none';
@@ -1320,8 +1353,7 @@ function automaticHookMode(ctx) {
const activeRoot = path.resolve(ctx.projectRoot || process.cwd());
if (!hookEnabledAt(activeRoot)) return 'none';
const manifests = HOOK_MANIFESTS_BY_PROVIDER[IMPECCABLE_PROVIDER_ID] || [];
const roots = [...new Set([process.cwd(), ctx.projectRoot, ctx.repoRoot].filter(Boolean).map((root) => path.resolve(root)))];
for (const root of roots) {
for (const root of hookManifestSearchRoots(ctx)) {
for (const rel of manifests) {
const raw = readJson(path.join(root, rel));
if (raw?.hooks && valueHasHookMarker(raw.hooks)) {
+12 -4
View File
@@ -18,8 +18,9 @@
* Covers:
* - Inline Next.js headers() with CSP string
* - Nuxt routeRules / nitro.routeRules CSP headers
* - "middleware": CSP set dynamically in middleware.{ts,js}.
* Detected but not auto-patched in v1.
* - "middleware": CSP set dynamically in middleware.{ts,js,mjs} or
* Next.js 16's proxy.{ts,js,mjs} convention. Detected
* but not auto-patched in v1.
* - "meta-tag": <meta http-equiv="Content-Security-Policy"> in
* layout files. Detected but not auto-patched in v1.
* - null: no CSP signals found; no patch needed.
@@ -77,6 +78,14 @@ const NUXT_ROUTE_RULES_SIGNALS = [
/\bscript-src\b/,
];
const NEXT_REQUEST_HOOK_FILES = new Set([
'middleware.ts',
'middleware.js',
'middleware.mjs',
'proxy.ts',
'proxy.js',
'proxy.mjs',
]);
const MIDDLEWARE_HINT = /headers\.set\(\s*["']Content-Security-Policy["']/i;
const META_TAG_HINT = /http-equiv\s*=\s*["']Content-Security-Policy["']/i;
@@ -133,8 +142,7 @@ export function detectCsp(cwd = process.cwd()) {
// === detect-only shapes ===
if ((base === 'middleware.ts' || base === 'middleware.js' || base === 'middleware.mjs') &&
MIDDLEWARE_HINT.test(body)) {
if (NEXT_REQUEST_HOOK_FILES.has(base) && MIDDLEWARE_HINT.test(body)) {
hits.middleware.push(relPath);
}