Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions backend/app/api/v1/config_repos.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ def _auth_ref_exists(auth_ref: str) -> bool:
return False
override = os.environ.get("RELYLOOP_SECRETS_DIR")
secrets_root = Path(override).resolve() if override else Path("./secrets").resolve()
# Path containment guard. `auth_ref` is already constrained to
# `^[a-zA-Z0-9_-]+$` by CreateConfigRepoRequest (no slashes/dots → no
# traversal at the API boundary); this resolve()+relative_to() is a second
# layer that also catches symlink escape and any non-HTTP caller. CodeQL's
# py/path-injection flags this candidate/is_file() pair because it doesn't
# model the Pydantic pattern or recognize relative_to() as a sanitizer —
# dismissed as a reviewed false positive (the input cannot escape
# secrets_root).
candidate = (secrets_root / auth_ref).resolve()
try:
candidate.relative_to(secrets_root)
Expand Down
11 changes: 9 additions & 2 deletions ui/scripts/gen-types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* Or via the package script: pnpm types:gen
*/

import { execSync } from 'node:child_process';
import { execFileSync } from 'node:child_process';
import { readFileSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
Expand All @@ -40,7 +40,14 @@ const BANNER = `// SPDX-FileCopyrightText: 2026 soundminds.ai
`;

console.log(`Generating ${OUTPUT} from ${SOURCE_URL}…`);
execSync(`npx openapi-typescript ${SOURCE_URL} -o ${OUTPUT}`, {
// execFileSync (no shell) instead of execSync with an interpolated string:
// SOURCE_URL comes from the OPENAPI_URL env var, and an interpolated shell
// command would let a crafted value inject. Passing args as an array runs the
// binary directly with no shell, so there is nothing to inject into. On Windows
// the launcher is `npx.cmd` (no shell to resolve the `.cmd` extension), so pick
// the platform-correct executable name.
const NPX = process.platform === 'win32' ? 'npx.cmd' : 'npx';
execFileSync(NPX, ['openapi-typescript', SOURCE_URL, '-o', OUTPUT], {
stdio: 'inherit',
cwd: UI_ROOT,
});
Expand Down
Loading