reporting-governance: enforce profile binding path boundary
This commit is contained in:
@@ -24,7 +24,26 @@ function assertObjectRecord(value, label) {
|
||||
return value;
|
||||
}
|
||||
|
||||
export function validateDeploymentProfileArtifact(artifact) {
|
||||
function assertRelativePathWithinRoot(relativePath, label, { root }) {
|
||||
const normalizedPath = assertNonEmptyString(relativePath, label);
|
||||
if (path.isAbsolute(normalizedPath)) {
|
||||
throw new Error(`${label} must stay within repo root: absolute paths are not allowed`);
|
||||
}
|
||||
|
||||
const resolvedPath = path.resolve(root, normalizedPath);
|
||||
const relativeToRoot = path.relative(root, resolvedPath);
|
||||
if (
|
||||
relativeToRoot === '..'
|
||||
|| relativeToRoot.startsWith(`..${path.sep}`)
|
||||
|| path.isAbsolute(relativeToRoot)
|
||||
) {
|
||||
throw new Error(`${label} must stay within repo root: path escapes root boundary`);
|
||||
}
|
||||
|
||||
return normalizedPath;
|
||||
}
|
||||
|
||||
export function validateDeploymentProfileArtifact(artifact, { repoRootOverride } = {}) {
|
||||
if (!artifact || typeof artifact !== 'object' || Array.isArray(artifact)) {
|
||||
throw new Error('deployment profile artifact must be an object');
|
||||
}
|
||||
@@ -40,16 +59,18 @@ export function validateDeploymentProfileArtifact(artifact) {
|
||||
throw new Error('deployment profile artifact bindings are required');
|
||||
}
|
||||
|
||||
assertNonEmptyString(bindings.entrypoint, 'deployment profile artifact spec.bindings.entrypoint');
|
||||
const root = path.resolve(repoRootOverride ?? repoRoot);
|
||||
const scripts = assertObjectRecord(bindings.scripts, 'deployment profile artifact spec.bindings.scripts');
|
||||
const artifactRoots = assertObjectRecord(bindings.artifact_roots, 'deployment profile artifact spec.bindings.artifact_roots');
|
||||
|
||||
assertRelativePathWithinRoot(bindings.entrypoint, 'deployment profile artifact spec.bindings.entrypoint', { root });
|
||||
assertNonEmptyString(artifact?.spec?.package?.pluginVersion, 'deployment profile artifact spec.package.pluginVersion');
|
||||
|
||||
for (const [key, relativePath] of Object.entries(scripts)) {
|
||||
assertNonEmptyString(relativePath, `deployment profile artifact spec.bindings.scripts.${key}`);
|
||||
assertRelativePathWithinRoot(relativePath, `deployment profile artifact spec.bindings.scripts.${key}`, { root });
|
||||
}
|
||||
for (const [key, relativePath] of Object.entries(artifactRoots)) {
|
||||
assertNonEmptyString(relativePath, `deployment profile artifact spec.bindings.artifact_roots.${key}`);
|
||||
assertRelativePathWithinRoot(relativePath, `deployment profile artifact spec.bindings.artifact_roots.${key}`, { root });
|
||||
}
|
||||
|
||||
return artifact;
|
||||
@@ -72,7 +93,7 @@ export function loadDeploymentProfileArtifact({ artifactPath, profileId } = {})
|
||||
}
|
||||
|
||||
export function createDeploymentBindingContract({ artifact, repoRootOverride } = {}) {
|
||||
const validatedArtifact = validateDeploymentProfileArtifact(artifact);
|
||||
const validatedArtifact = validateDeploymentProfileArtifact(artifact, { repoRootOverride });
|
||||
const root = path.resolve(repoRootOverride ?? repoRoot);
|
||||
const scripts = Object.fromEntries(
|
||||
Object.entries(validatedArtifact.spec.bindings.scripts).map(([key, relativePath]) => [key, path.resolve(root, relativePath)])
|
||||
|
||||
Reference in New Issue
Block a user