@pho9ubenaa/siro 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ import{closeSync,constants,fstatSync,lstatSync,openSync,opendirSync,readSync,realpathSync,statSync}from"node:fs";import path from"node:path";import{randomUUID}from"node:crypto";import{pathToFileURL}from"node:url";import satisfies from"semver/functions/satisfies.js";import*as vb from"valibot";import{lt,parse}from"semver";import valid from"semver/functions/valid.js";import{Minimatch}from"minimatch";import ini from"ini";import{parse as parse$1}from"smol-toml";import{parseDocument}from"yaml";import{types}from"node:util";import pc from"picocolors";var SiroError=class extends Error{exitCode;constructor(message,exitCode){super(message);this.name=`SiroError`;this.exitCode=exitCode}};var ConfigError=class extends SiroError{constructor(message){super(message,2);this.name=`ConfigError`}};var UsageError=class extends SiroError{constructor(message){super(message,2);this.name=`UsageError`}};var ConfigParseError=class extends Error{};const wrapCodecError=(filePath,parse)=>{try{return parse()}catch(error){if(error instanceof ConfigError)throw error;throw new ConfigError(`${filePath}: ${error instanceof ConfigParseError?error.message:`Invalid configuration.`}`)}};const isOwnKey=(record,key)=>Object.hasOwn(record,key);const isPlainRecord=value=>{if(typeof value!==`object`||value===null)return false;const prototype=Object.getPrototypeOf(value);return prototype===Object.prototype||prototype===null};const DEFAULT_SCAN_LIMITS=Object.freeze({maxFileBytes:8*1024*1024,maxTotalBytes:64*1024*1024,maxEntries:1e5,maxDirectories:1e4,maxDirectoryDepth:128,maxConfigDepth:128,maxFindings:5e4,maxOutputBytes:32*1024*1024});const resolveScanLimits=input=>{if(input!==void 0&&(!isPlainRecord(input)||Object.keys(input).some(key=>!Object.hasOwn(DEFAULT_SCAN_LIMITS,key))))throw new UsageError(`limits must be an object of known scan limits.`);const limits={...DEFAULT_SCAN_LIMITS,...input};for(const[key,value]of Object.entries(limits))if(!Number.isSafeInteger(value)||value<=0)throw new UsageError(`${key} must be a positive safe integer.`);return limits};const SCAN_SCOPE_ADVICE=`Reduce the scan scope. If the target contains independent projects, scan each project directory separately.`;const checkLimit=(name,value,limits)=>{if(value>limits[name]){const advice=name===`maxFileBytes`?`Reduce the size of the input file.`:SCAN_SCOPE_ADVICE;throw new ConfigError(`Inspection exceeds ${name} (${limits[name]}). ${advice}`)}};const utf8Bytes=text=>{let bytes=0;for(const character of text){const code=character.codePointAt(0)??0;if(code<=127)bytes+=1;else if(code<=2047)bytes+=2;else if(code<=65535)bytes+=3;else bytes+=4}return bytes};const checkConfigDepth=(value,maxDepth)=>{const activeAncestors=new Set;const deepestVisitedDepth=new Map;const frames=[{kind:`enter`,value,depth:1}];for(;;){const frame=frames.pop();if(frame===void 0)break;if(frame.kind===`leave`){activeAncestors.delete(frame.value);continue}if(frame.value===null||typeof frame.value!==`object`)continue;if(activeAncestors.has(frame.value))throw new ConfigError(`Configuration contains a circular reference.`);if(frame.depth>maxDepth)throw new ConfigError(`Configuration exceeds maxConfigDepth (${maxDepth}). Simplify the nesting of the configuration.`);if((deepestVisitedDepth.get(frame.value)??0)>=frame.depth)continue;deepestVisitedDepth.set(frame.value,frame.depth);activeAncestors.add(frame.value);frames.push({kind:`leave`,value:frame.value});for(const child of Object.values(frame.value))frames.push({kind:`enter`,value:child,depth:frame.depth+1})}};const outputBudget=maxBytes=>{let bytes=0;return text=>{bytes+=utf8Bytes(text);if(bytes>maxBytes)throw new SiroError(`Output exceeds maxOutputBytes (${maxBytes}). ${SCAN_SCOPE_ADVICE}`,70)}};const isNodeError=value=>value instanceof Error&&`code`in value&&typeof value.code===`string`;const createStrictPathChecker=root=>{const selectedRoot=path.resolve(root);if(lstatSync(selectedRoot).isSymbolicLink())throw new ConfigError(`Strict filesystem rejects a symlink cwd.`);const canonicalRoot=realpathSync(selectedRoot);return file=>{const relative=path.relative(root,file);if(relative===`..`||relative.startsWith(`..${path.sep}`)||path.isAbsolute(relative))throw new ConfigError(`Strict filesystem path escapes cwd.`);let current=canonicalRoot;for(const component of relative.split(path.sep)){if(component===``)continue;current=path.join(current,component);if(lstatSync(current).isSymbolicLink())throw new ConfigError(`${file}: strict filesystem rejects symlinks.`)}}};const createNodeFileSystem=(limits=DEFAULT_SCAN_LIMITS,strictRoot)=>{const checkPath=strictRoot===void 0?()=>{}:createStrictPathChecker(strictRoot);const READ_CHUNK_BYTES=64*1024;let totalBytesRead=0;let directoryEntryCount=0;const statRegularFile=file=>{checkPath(file);const stat=statSync(file);if(!stat.isFile())throw new ConfigError(`${file}: expected a regular file.`);return stat};const openInputFile=file=>{try{const stat=statRegularFile(file);checkLimit(`maxFileBytes`,stat.size,limits);checkLimit(`maxTotalBytes`,totalBytesRead+stat.size,limits);return openSync(file,constants.O_RDONLY|constants.O_NONBLOCK|(strictRoot===void 0?0:constants.O_NOFOLLOW??0))}catch(error){if(isNodeError(error)&&error.code===`ENOENT`)return;throw error}};return{readDirectories(directory){checkPath(directory);const stream=opendirSync(directory);const names=[];try{for(;;){const entry=stream.readSync();if(entry===null)break;directoryEntryCount+=1;checkLimit(`maxEntries`,directoryEntryCount,limits);if(entry.isDirectory())names.push(entry.name)}}finally{stream.closeSync()}return names},exists(file){try{statRegularFile(file);return true}catch(error){if(isNodeError(error)&&error.code===`ENOENT`)return false;throw error}},readText(file){const fd=openInputFile(file);if(fd===void 0)return;try{if(!fstatSync(fd).isFile())throw new ConfigError(`${file}: expected a regular file.`);const chunks=[];let fileBytesRead=0;for(;;){const remainingFileBytes=limits.maxFileBytes-fileBytesRead;const remainingTotalBytes=limits.maxTotalBytes-totalBytesRead;const nextReadBytes=Math.min(READ_CHUNK_BYTES,remainingFileBytes+1,remainingTotalBytes+1);const buffer=Buffer.allocUnsafe(nextReadBytes);const bytesRead=readSync(fd,buffer);if(bytesRead===0)break;fileBytesRead+=bytesRead;totalBytesRead+=bytesRead;checkLimit(`maxFileBytes`,fileBytesRead,limits);checkLimit(`maxTotalBytes`,totalBytesRead,limits);chunks.push(buffer.subarray(0,bytesRead))}return Buffer.concat(chunks,fileBytesRead).toString(`utf8`)}catch(error){if(isNodeError(error)&&error.code===`ENOENT`)return;throw error}finally{closeSync(fd)}}}};const nodeFileSystem={readDirectories:directory=>createNodeFileSystem().readDirectories(directory),exists:file=>createNodeFileSystem().exists(file),readText:file=>createNodeFileSystem().readText(file)};const assertDirectory=root=>{if(!statSync(root).isDirectory())throw new UsageError(`The lint target must be a directory.`)};const isSlashRelativePath=value=>typeof value===`string`&&value.length>0&&!value.includes(`\0`)&&!value.startsWith(`/`)&&!value.split(`/`).includes(`..`);const joinRelativePath=(parent,child)=>{const joined=parent===`.`?child:`${parent}/${child}`;if(!isSlashRelativePath(joined))throw new TypeError(`Invalid composed repository path.`);return joined};const isRelPath=value=>typeof value===`string`&&value.length>0&&!value.includes(`\0`)&&!/^[\\/]/u.test(value)&&!/^[a-z]:/iu.test(value)&&!value.split(/[\\/]/u).includes(`..`);const asRelPath=value=>{if(!isRelPath(value))throw new TypeError(`Expected a repository-relative path without parent traversal.`);return value};const isAbsPath=value=>typeof value===`string`&&!value.includes(`\0`)&&path.isAbsolute(value);const asAbsPath=value=>{if(!isAbsPath(value))throw new TypeError(`Expected an absolute filesystem path.`);return value};const isNativeChildName=value=>typeof value===`string`&&value.length>0&&value!==`.`&&value!==`..`&&!value.includes(`\0`)&&!value.includes(`/`)&&(path.sep!==`\\`||!value.includes(`\\`)&&!value.includes(`:`));const nodePaths={isAbsolute:isAbsPath,resolve(root,relative){if(typeof relative!==`string`||relative.includes(`\0`)||path.isAbsolute(relative)||relative.split(path.sep===`\\`?/[\\/]/u:/\//u).includes(`..`))throw new ConfigError(`Invalid repository-relative path: ${String(relative)}`);const resolved=path.join(root,relative);const relativeToRoot=path.relative(root,resolved);if(relativeToRoot===`..`||relativeToRoot.startsWith(`..${path.sep}`)||path.isAbsolute(relativeToRoot))throw new ConfigError(`Path escapes repository root.`);return asAbsPath(resolved)},child(parent,name){if(!isNativeChildName(name))throw new ConfigError(`FileSystem.readDirectories returned an invalid child directory name: ${String(name)}`);return joinRelativePath(parent,name)}};var version$1=`0.6.2`;var engines={"node":`^22.18.0 || ^24.0.0`};const version=version$1;const SUPPORTED_NODE_RANGE=engines.node;const isSupportedNodeVersion=version=>satisfies(version,SUPPORTED_NODE_RANGE);const PMS=[`npm`,`pnpm`,`yarn`,`bun`,`deno`,`aube`];const SEVERITIES=[`error`,`warn`,`info`];const SEVERITY_RANK={error:3,info:1,warn:2};const PM_SET=new Set(PMS);const SEVERITY_SET=new Set(SEVERITIES);const isPM=value=>PM_SET.has(value);const isSeverity=value=>SEVERITY_SET.has(value);const PACKAGE_MANAGER_FIELD=/^(?<pmName>[a-z]+)(?:@.+)?$/u;const parsePackageManagerField=value=>{const name=PACKAGE_MANAGER_FIELD.exec(value.trim())?.groups?.pmName;if(typeof name!==`undefined`&&isPM(name))return name};const isStableVersion=value=>{if(typeof value!==`string`||!/^\d/u.test(value)||value.trim()!==value)return false;const version=parse(value);return version!==null&&version.prerelease.length===0};const declaredPMVersion=declaration=>{const[pm,version,...extra]=declaration?.trim().split(`@`)??[];return pm&&isPM(pm)&&isStableVersion(version)&&extra.length===0?{[pm]:version}:{}};const requireDenseArray=(value,name)=>{if(!Array.isArray(value))throw new ConfigError(`${name} must be a dense array.`);for(let index=0;index<value.length;index++)if(!Object.hasOwn(value,index))throw new ConfigError(`${name} must be a dense array.`);return value};const normalizeScopePath=(value,name)=>{const invalidPattern=name===`exclude`&&typeof value===`string`&&(value.includes(`\\`)||/^[a-z]:/iu.test(value));if(typeof value!==`string`||!value||value.includes(`\0`)||invalidPattern||/^[\\/]/u.test(value)||/^[a-z]:[\\/]/iu.test(value)||value.split(`/`).includes(`..`))throw new ConfigError(`${name}: expected a nonempty cwd-relative /-separated path without parent traversal.`);return value.split(`/`).filter(part=>part&&part!==`.`).join(`/`)||`.`};const parseExcludes=value=>{const patterns=requireDenseArray(value,`exclude`).map(input=>{const pattern=normalizeScopePath(input,`exclude`);if(pattern===`.`||pattern.startsWith(`!`))throw new ConfigError(`exclude cannot exclude cwd (.) or re-include with leading !.`);return pattern});return[...new Set(patterns)]};const parseInstallationRoots=value=>{const roots=new Map;for(const input of requireDenseArray(value,`installationRoots`)){if(typeof input!==`string`&&(!isPlainRecord(input)||Object.keys(input).some(key=>![`path`,`pm`,`pmVersion`].includes(key))))throw new ConfigError(`installationRoots entries must be paths or { path, pm?, pmVersion? }.`);const entry=typeof input===`string`?{path:input}:input;const path=normalizeScopePath(entry.path,`installationRoots`);const{pm,pmVersion}=entry;if(pm!==void 0&&(typeof pm!==`string`||!isPM(pm))||pmVersion!==void 0&&(!pm||!isStableVersion(pmVersion)))throw new ConfigError(`${path}: pmVersion requires pm and an exact stable version; pm must be a supported manager.`);if(path===`.`&&(pm!==void 0||pmVersion!==void 0))throw new ConfigError(`Use top-level pm / pmVersion / pms / pmVersions for installation root ".".`);const root={path,pm,pmVersion};const previous=roots.get(path);if(previous&&(previous.pm!==root.pm||previous.pmVersion!==root.pmVersion))throw new ConfigError(`${path}: conflicting installationRoots entries.`);roots.set(path,root)}return[...roots.values()]};const PROJECT_TYPES=[`application`,`package`];const isProjectType=value=>PROJECT_TYPES.some(projectType=>projectType===value);const isReporterShape=value=>{return isPlainRecord(value)&&typeof value.name===`string`&&typeof value.format===`function`};const CODEC_KINDS=[`json`,`npmrc`,`toml`,`yaml`];const getByPath=(config,keyPath)=>{let current=config;for(const key of keyPath){if(!isPlainRecord(current)||!Object.hasOwn(current,key))return void 0;current=current[key]}return current};const toParsedConfig=value=>{if(isPlainRecord(value))return value;throw new ConfigParseError(`Config root must be a mapping.`)};const CONFIG_FILE_KINDS=new Set(CODEC_KINDS);const isConfigFileRefShape=value=>{if(!isPlainRecord(value)||typeof value.kind!==`string`||!isRelPath(value.path))return false;return CONFIG_FILE_KINDS.has(value.kind)};const isArrayOf=(value,isItem)=>{if(!Array.isArray(value))return false;const length=value.length;for(let index=0;index<length;index++)if(!Object.hasOwn(value,index)||!isItem(value[index]))return false;return true};const isStringArray=value=>isArrayOf(value,item=>typeof item===`string`);const defineRule=rule=>rule;const isOptionalString=value=>typeof value===`undefined`||typeof value===`string`;const isConfigValueShape=value=>typeof value===`string`||typeof value===`boolean`||typeof value===`number`&&Number.isFinite(value);const isCheckStatusShape=value=>{if(!isPlainRecord(value))return false;if(value.state===`ok`||value.state===`na`)return true;if(value.state===`violations`)return isArrayOf(value.violations,isViolationStatusShape)&&value.violations.length>0;return isViolationStatusShape(value)};const isViolationStatusShape=value=>{if(!isPlainRecord(value))return false;return value.state===`violation`&&typeof value.message===`string`&&(value.file===void 0||isRelPath(value.file))&&(value.expected===void 0||isConfigValueShape(value.expected))&&(value.severity===void 0||typeof value.severity===`string`&&isSeverity(value.severity))&&!(`manualSteps`in value)&&!(`fix`in value)&&!(`fixable`in value)&&(value.remediation===void 0||isRemediationShape(value.remediation))};const isSetKeyOperation=value=>isPlainRecord(value)&&value.op===`setKey`&&isConfigFileRefShape(value.file)&&isStringArray(value.keyPath)&&value.keyPath.length>0&&isConfigValueShape(value.value);const isRemediationShape=value=>{if(!isPlainRecord(value))return false;if(value.kind===`manual`)return!(`operations`in value)&&isStringArray(value.steps)&&value.steps.length>0;return value.kind===`automatic`&&!(`steps`in value)&&isArrayOf(value.operations,isSetKeyOperation)&&value.operations.length>0};const isVersionNoteShape=value=>typeof value===`undefined`||isPlainRecord(value)&&isOptionalString(value.configAvailableSince)&&isOptionalString(value.defaultSafeSince)&&isOptionalString(value.note);const isRuleBindingShape=value=>{if(!isPlainRecord(value))return false;return(value.file===void 0||isConfigFileRefShape(value.file))&&!(`fix`in value)&&!(`fixKind`in value)&&!(`fileGlob`in value)&&isOptionalString(value.docs)&&(typeof value.severity===`undefined`||typeof value.severity===`string`&&isSeverity(value.severity))&&isVersionNoteShape(value.versionNote)&&typeof value.check===`function`};const isBindingsShape=value=>isPlainRecord(value)&&Object.keys(value).every(isPM)&&PMS.every(pm=>value[pm]===void 0||isRuleBindingShape(value[pm]));const isProjectTypeValue=value=>typeof value===`string`&&isProjectType(value);const isProjectTypesShape=value=>typeof value===`undefined`||isArrayOf(value,isProjectTypeValue);const isRuleShape=value=>{if(!isPlainRecord(value))return false;return typeof value.id===`string`&&typeof value.title===`string`&&typeof value.description===`string`&&typeof value.severity===`string`&&isSeverity(value.severity)&&isOptionalString(value.docs)&&isProjectTypesShape(value.projectTypes)&&isBindingsShape(value.bindings)};const assertSynchronous=(value,label)=>{if(value!==null&&(typeof value===`object`||typeof value===`function`)&&`then`in value&&typeof value.then===`function`){Promise.resolve(value).catch(()=>{});throw new ConfigError(`${label} must be synchronous; Promise/thenable values are not supported.`)}};const RuleSettingSchema=vb.union([vb.picklist(SEVERITIES),vb.literal(`off`)]);const RuleOverridesSchema=vb.pipe(vb.custom(isPlainRecord,`must be an object of rule settings`),vb.rawTransform(({dataset,addIssue})=>{const entries=[];for(const[key,value]of Object.entries(dataset.value)){const result=vb.safeParse(RuleSettingSchema,value);if(result.success)entries.push([key,result.output]);else addIssue({message:result.issues[0].message,path:[{input:dataset.value,key,origin:`value`,type:`object`,value}]})}return Object.fromEntries(entries)}));const ConfigSchema=vb.strictObject({exclude:vb.optional(vb.custom(value=>{parseExcludes(value);return true})),installationRoots:vb.optional(vb.custom(value=>{parseInstallationRoots(value);return true})),customRules:vb.optional(vb.array(vb.custom(isRuleShape,`must be a structurally valid rule`))),pms:vb.optional(vb.pipe(vb.array(vb.picklist(PMS)),vb.minLength(1,`must not be empty (omit the key to auto-detect, or list at least one PM)`))),projectType:vb.optional(vb.picklist(PROJECT_TYPES)),pmVersions:vb.optional(vb.pipe(vb.custom(isPlainRecord,`must be an object of PM versions`),vb.check(value=>Object.keys(value).every(isPM),`unknown package manager in pmVersions`),vb.record(vb.picklist(PMS),vb.custom(isStableVersion,`must be an exact stable version such as 10.16.0`)))),reporters:vb.optional(vb.array(vb.custom(isReporterShape,`must be a { name, format } reporter`))),rules:vb.optional(RuleOverridesSchema)},`unknown config key (check for a typo)`);const formatIssues=issues=>issues.map(issue=>{const keys=[];for(const segment of issue.path??[]){const key=segment.key;if(typeof key===`string`||typeof key===`number`)keys.push(key)}const keyPath=keys.join(`.`);if(keyPath)return`${keyPath}: ${issue.message}`;return issue.message}).join(`; `);const parseConfig=(candidate,name=`siro.config`)=>{assertSynchronous(candidate,name);if(!isPlainRecord(candidate))throw new ConfigError(`${name} must export a config object (got ${Array.isArray(candidate)?`an array`:typeof candidate}).`);const result=vb.safeParse(ConfigSchema,Object.fromEntries(Object.entries(candidate)));if(!result.success)throw new ConfigError(`${name}: ${formatIssues(result.issues)}`);return result.output};const CONFIG_NAMES=[`siro.config.ts`,`siro.config.mjs`,`siro.config.js`];const describeError=error=>error instanceof Error?error.message:String(error);const importConfig=async(url,name)=>{try{return await import(url.href)}catch(error){throw new ConfigError(`Failed to load ${name}: ${describeError(error)}`)}};const loadConfig=async(cwd,nodeVersion=process.versions.node)=>{const name=CONFIG_NAMES.find(candidate=>nodeFileSystem.exists(asAbsPath(path.join(cwd,candidate))));if(name===void 0)return;if(name.endsWith(`.ts`)&&!isSupportedNodeVersion(nodeVersion))throw new ConfigError(`${name} requires Node.js with native type stripping (${SUPPORTED_NODE_RANGE}); current is v${nodeVersion}. Rename the config to siro.config.mjs (plain JS) or upgrade Node.js.`);const url=pathToFileURL(path.join(cwd,name));url.searchParams.set(`siro-load`,randomUUID());const module=await importConfig(url,name);return parseConfig(module!==null&&typeof module===`object`&&`default`in module?module.default:module,name)};const CONFIG_FILES={aubeWorkspace:{kind:`yaml`,path:asRelPath(`aube-workspace.yaml`)},bunfig:{kind:`toml`,path:asRelPath(`bunfig.toml`)},denoJson:{kind:`json`,path:asRelPath(`deno.json`)},npmrc:{kind:`npmrc`,path:asRelPath(`.npmrc`)},packageJson:{kind:`json`,path:asRelPath(`package.json`)},pnpmWorkspace:{kind:`yaml`,path:asRelPath(`pnpm-workspace.yaml`)},yarnrc:{kind:`yaml`,path:asRelPath(`.yarnrc.yml`)}};const{npmrc:npmrc$7,packageJson:packageJson$2,pnpmWorkspace:pnpmWorkspace$11,yarnrc:yarnrc$10,bunfig:bunfig$6}=CONFIG_FILES;const settingAvailability=[{pm:`npm`,file:npmrc$7,keyPath:[`provenance`],since:`9.5.0`,source:`https://github.com/npm/cli/releases/tag/v9.5.0`},{pm:`npm`,file:packageJson$2,keyPath:[`publishConfig`,`provenance`],since:`9.5.0`,source:`https://github.com/npm/cli/releases/tag/v9.5.0`},{pm:`npm`,file:npmrc$7,keyPath:[`min-release-age`],since:`11.10.0`,source:`https://github.com/npm/cli/releases/tag/v11.10.0`},{pm:`pnpm`,file:pnpmWorkspace$11,keyPath:[`strictDepBuilds`],since:`10.6.0`,source:`https://github.com/pnpm/pnpm/releases/tag/v10.6.0`},{pm:`pnpm`,file:pnpmWorkspace$11,keyPath:[`dangerouslyAllowAllBuilds`],since:`10.9.0`,source:`https://github.com/pnpm/pnpm/releases/tag/v10.9.0`},{pm:`pnpm`,file:pnpmWorkspace$11,keyPath:[`minimumReleaseAge`],since:`10.16.0`,source:`https://github.com/pnpm/pnpm/releases/tag/v10.16.0`},{pm:`pnpm`,file:pnpmWorkspace$11,keyPath:[`minimumReleaseAgeExclude`],since:`10.16.0`,source:`https://github.com/pnpm/pnpm/releases/tag/v10.16.0`},{pm:`pnpm`,file:pnpmWorkspace$11,keyPath:[`trustPolicy`],since:`10.21.0`,source:`https://github.com/pnpm/pnpm/releases/tag/v10.21.0`},{pm:`pnpm`,file:pnpmWorkspace$11,keyPath:[`blockExoticSubdeps`],since:`10.26.0`,source:`https://github.com/pnpm/pnpm/releases/tag/v10.26.0`},{pm:`pnpm`,file:pnpmWorkspace$11,keyPath:[`frozenStore`],since:`11.7.0`,source:`https://github.com/pnpm/pnpm/releases/tag/v11.7.0`},{pm:`yarn`,file:yarnrc$10,keyPath:[`enableHardenedMode`],since:`4.0.0`,source:`https://yarnpkg.com/blog/release/4.0`},{pm:`yarn`,file:yarnrc$10,keyPath:[`npmMinimalAgeGate`],since:`4.10.0`,source:`https://github.com/yarnpkg/berry/releases/tag/@yarnpkg/cli/4.10.0`},{pm:`yarn`,file:yarnrc$10,keyPath:[`npmPreapprovedPackages`],since:`4.10.0`,source:`https://github.com/yarnpkg/berry/releases/tag/@yarnpkg/cli/4.10.0`},{pm:`bun`,file:bunfig$6,keyPath:[`install`,`minimumReleaseAge`],since:`1.3.0`,source:`https://bun.com/blog/bun-v1.3#minimum-release-age`},{pm:`bun`,file:bunfig$6,keyPath:[`install`,`security`,`scanner`],since:`1.2.21`,source:`https://bun.com/blog/bun-v1.2.21#security-scanner-api-for-bun-install`},{pm:`deno`,file:npmrc$7,keyPath:[`min-release-age`],since:`2.8.1`,source:`https://github.com/denoland/deno/releases/tag/v2.8.1`}];const settingsFor=pm=>settingAvailability.filter(setting=>setting.pm===pm);const settingAvailabilityByPM={npm:settingsFor(`npm`),pnpm:settingsFor(`pnpm`),yarn:settingsFor(`yarn`),bun:settingsFor(`bun`),deno:settingsFor(`deno`),aube:settingsFor(`aube`)};const sameSetting=(setting,target)=>target.file.path===setting.file.path&&target.file.kind===setting.file.kind&&target.keyPath.length===setting.keyPath.length&&target.keyPath.every((key,index)=>key===setting.keyPath[index]);const settingSupportedByTarget=(pm,version,target)=>{if(version===void 0)return void 0;const availability=settingAvailabilityByPM[pm].find(setting=>sameSetting(setting,target));return availability===void 0?void 0:!lt(version,availability.since)};const guardRemediationAvailability=(pm,version,remediation,targets=remediation?.kind===`automatic`?remediation.operations:[])=>{if(version===void 0||remediation===void 0)return remediation;const requirements=[];for(const setting of settingAvailabilityByPM[pm]){if(!lt(version,setting.since)||!targets.some(target=>sameSetting(setting,target)))continue;requirements.push(`${setting.file.path}#${setting.keyPath.join(`.`)} requires ${pm} >=${setting.since}`)}if(requirements.length===0)return remediation;const stepsAfterUpgrade=remediation.kind===`manual`?remediation.steps:remediation.operations.map(operation=>`After upgrading, set ${operation.file.path}#${operation.keyPath.join(`.`)} to ${JSON.stringify(operation.value)}.`);return{kind:`manual`,steps:[`Target ${pm} ${version} does not support this proposal: ${requirements.join(`; `)}. Verify and upgrade the target before applying the steps below, or choose a supported security control. Removing a setting alone does not provide its protection.`,...stepsAfterUpgrade]}};const proposeChanges=(config,operations)=>{for(const operation of operations){let current=config;for(const key of operation.keyPath){if(current===void 0)break;if(!isPlainRecord(current))return{kind:`manual`,steps:[`Review ${operation.file.path}: ${operation.keyPath.join(`.`)} has a non-object parent. Restructure it before setting the value to ${JSON.stringify(operation.value)}.`]};current=Object.hasOwn(current,key)?current[key]:void 0}if(current!==null&&typeof current===`object`)return{kind:`manual`,steps:[`Review ${operation.file.path}: ${operation.keyPath.join(`.`)} contains nested settings. Preserve the required entries when correcting this value.`]}}return{kind:`automatic`,operations}};const overrideBindings=(rule,overrides)=>({...rule,bindings:{...rule.bindings,...overrides}});const accepts=(spec,actual)=>spec.accept?spec.accept(actual):actual===spec.value;const checkKeyValue=(spec,config)=>{const actual=getByPath(config,spec.keyPath);const isCoveredByUnconditionalDefault=actual===void 0&&spec.documentedDefault!==void 0&&spec.defaultSafety===`unconditional`&&accepts(spec,spec.documentedDefault);if(!isCoveredByUnconditionalDefault&&accepts(spec,actual))return{state:`ok`};const severity=isCoveredByUnconditionalDefault?spec.defaultSatisfiedSeverity??`info`:void 0;if(severity===`off`)return{state:`ok`};return{state:`violation`,actual,expected:spec.value,message:spec.message,...severity===void 0?{}:{severity},remediation:proposeChanges(config,[{file:spec.file,keyPath:spec.keyPath,op:`setKey`,value:spec.value}])}};const buildBinding=(spec,pm,applies)=>({check(ctx,config){if(typeof applies!==`undefined`&&!applies(ctx))return{state:`na`};const status=checkKeyValue(spec,config);if(status.state!==`violation`)return status;return{...status,remediation:guardRemediationAvailability(pm,ctx.pmVersion,status.remediation,[spec])}},docs:spec.docs,file:spec.file,severity:spec.severity,versionNote:spec.versionNote});const requireConfigKey=options=>{const bindings={};for(const pm of PMS){const spec=options.bindings[pm];if(spec===void 0)continue;if(`extraFix`in spec)throw new TypeError(`extraFix is no longer supported; use a custom binding.`);bindings[pm]=buildBinding(spec,pm,options.applies)}return{bindings,description:options.description,docs:options.docs,id:options.id,projectTypes:options.projectTypes,severity:options.severity,title:options.title}};const withAubeParanoid=rule=>{const{aube}=rule.bindings;if(!aube)return rule;return overrideBindings(rule,{aube:{...aube,check(ctx,config){if(getByPath(config,[`paranoid`])===true)return{state:`ok`};return aube.check(ctx,config)}}})};const{aubeWorkspace:aubeWorkspace$8}=CONFIG_FILES;const builtRule$6=requireConfigKey({bindings:{aube:{accept:value=>value===`on`||value===`required`,docs:`https://aube.jdx.dev/security.html`,documentedDefault:`on`,defaultSafety:`unconditional`,file:aubeWorkspace$8,keyPath:[`advisoryCheck`],message:"Set `advisoryCheck: on` (or `required`) in aube-workspace.yaml to check dependencies against known-malicious advisories.",value:`on`}},description:`Query the OSV database for known-malicious packages during dependency resolution.`,docs:`https://aube.jdx.dev/security.html`,id:`advisory-check`,severity:`warn`,title:`Enable malicious-package advisory checks`});const advisoryCheck=withAubeParanoid(builtRule$6);const isNonBlankString=value=>typeof value===`string`&&value.trim().length>0;const isNonBlankStringArray=value=>isArrayOf(value,isNonBlankString);const isNonEmptyObject=value=>typeof value===`object`&&value!==null&&!Array.isArray(value)&&Object.keys(value).length>0;const{yarnrc:yarnrc$9}=CONFIG_FILES;const message="Set `approvedGitRepositories: []` in .yarnrc.yml to block all git: protocol dependencies (or list approved repository globs).";const yarnBinding$2={check(_ctx,config){const value=getByPath(config,[`approvedGitRepositories`]);if(isNonBlankStringArray(value))return{state:`ok`};return{remediation:{kind:`manual`,steps:["Add `approvedGitRepositories: []` to .yarnrc.yml to block all git deps, or list specific approved repository URL globs."]},actual:value,expected:`[]`,message,state:`violation`}},docs:`https://yarnpkg.com/configuration/yarnrc#approvedGitRepositories`,file:yarnrc$9,versionNote:{configAvailableSince:`yarn 4.14.0`}};const approvedGitRepos=defineRule({bindings:{yarn:yarnBinding$2},description:`Restrict git: protocol dependencies to an explicit allowlist of approved repository URL patterns.`,docs:`https://yarnpkg.com/configuration/yarnrc#approvedGitRepositories`,id:`approved-git-repos`,severity:`warn`,title:`Approve git repository dependencies`});const{yarnrc:yarnrc$8}=CONFIG_FILES;const isNonEmptyArray=value=>Array.isArray(value)&&value.length>0;const SUPPRESSION_KEYS=[`npmAuditIgnoreAdvisories`,`npmAuditExcludePackages`];const yarnBinding$1={check(_ctx,config){const suppressedKeys=SUPPRESSION_KEYS.filter(key=>isNonEmptyArray(getByPath(config,[key])));const[first]=suppressedKeys;if(first===void 0)return{state:`ok`};return{actual:getByPath(config,[first]),message:`Review audit suppression in .yarnrc.yml (${suppressedKeys.join(`, `)}). Broad glob patterns can silently hide future vulnerabilities.`,state:`violation`,remediation:{kind:`manual`,steps:["Review entries in `npmAuditIgnoreAdvisories` and `npmAuditExcludePackages` — remove stale suppressions and overly broad glob patterns."]}}},docs:`https://yarnpkg.com/configuration/yarnrc#npmAuditIgnoreAdvisories`,file:yarnrc$8};const auditSuppression=defineRule({bindings:{yarn:yarnBinding$1},description:`Flag audit advisory suppressions that may silently hide future vulnerabilities via broad glob patterns.`,docs:`https://yarnpkg.com/configuration/yarnrc#npmAuditIgnoreAdvisories`,id:`audit-suppression`,severity:`info`,title:`Review audit suppression entries`});const{bunfig:bunfig$5}=CONFIG_FILES;const blockAutoInstall=requireConfigKey({bindings:{bun:{docs:`https://bun.sh/docs/runtime/bunfig#install-auto`,file:bunfig$5,keyPath:[`install`,`auto`],message:'Set `auto = "disable"` under [install] in bunfig.toml to prevent auto-install from bypassing security guards.',value:`disable`,accept:value=>value===`disable`||value===false}},description:`Disable auto-install so dependencies are only installed through an explicit install step where security guards apply.`,docs:`https://bun.sh/docs/runtime/bunfig#install-auto`,id:`block-auto-install`,severity:`warn`,title:`Block automatic package installation`});const{aubeWorkspace:aubeWorkspace$7,npmrc:npmrc$6,pnpmWorkspace:pnpmWorkspace$10}=CONFIG_FILES;const isRootOrNone=value=>value===`root`||value===`none`;const npmMessage="Set `allow-git=none` and `allow-remote=none` in .npmrc to block git/tarball URLs; use `root` only when direct URL dependencies are required.";const npmBinding$2={check(_ctx,config){const git=getByPath(config,[`allow-git`]);const remote=getByPath(config,[`allow-remote`]);if(isRootOrNone(git)&&isRootOrNone(remote))return{state:`ok`};const invalidValue=[git,remote].find(value=>value!==void 0&&!isRootOrNone(value));return{state:`violation`,expected:`none`,...invalidValue===void 0?{}:{actual:invalidValue},message:npmMessage,remediation:proposeChanges(config,[{file:npmrc$6,keyPath:[`allow-git`],op:`setKey`,value:`none`},{file:npmrc$6,keyPath:[`allow-remote`],op:`setKey`,value:`none`}])}},docs:`https://docs.npmjs.com/cli/v12/using-npm/config#allow-git`,file:npmrc$6,versionNote:{defaultSafeSince:`npm 12.0.0`}};const builtRule$5=requireConfigKey({bindings:{aube:{docs:`https://aube.jdx.dev/security.html`,documentedDefault:true,defaultSafety:`unconditional`,file:aubeWorkspace$7,keyPath:[`blockExoticSubdeps`],message:"Set `blockExoticSubdeps: true` in aube-workspace.yaml to block git/tarball URLs in transitive dependencies.",value:true},pnpm:{docs:`https://pnpm.io/settings/dependency-resolution#blockexoticsubdeps`,documentedDefault:true,defaultSafety:`conditional`,file:pnpmWorkspace$10,keyPath:[`blockExoticSubdeps`],message:"Set `blockExoticSubdeps: true` in pnpm-workspace.yaml to block git/tarball URLs in transitive dependencies.",value:true,versionNote:{configAvailableSince:`pnpm 10.26.0`,defaultSafeSince:`pnpm 10.26.0`}}},description:`Refuse to install transitive dependencies sourced from git or tarball URLs, which bypass registry integrity checking.`,docs:`https://pnpm.io/settings/dependency-resolution#blockexoticsubdeps`,id:`block-exotic-subdeps`,severity:`warn`,title:`Block exotic subdependencies`});const blockExoticSubdeps=overrideBindings(builtRule$5,{npm:npmBinding$2});const{bunfig:bunfig$4}=CONFIG_FILES;const bunScannerBinding={check(ctx,config){const scanner=getByPath(config,[`install`,`security`,`scanner`]);if(isNonBlankString(scanner))return{state:`ok`};return{remediation:guardRemediationAvailability(`bun`,ctx.pmVersion,{kind:`manual`,steps:['Add `[install.security] scanner = "@socketsecurity/bun-security-scanner"` (or another bun-compatible scanner) to bunfig.toml.']},[{file:bunfig$4,keyPath:[`install`,`security`,`scanner`]}]),actual:scanner,message:'Configure `[install.security] scanner = "..."` in bunfig.toml (e.g. `@socketsecurity/bun-security-scanner`) to scan new packages on install.',state:`violation`}},docs:`https://bun.com/docs/pm/security-scanner-api`,file:bunfig$4,versionNote:{configAvailableSince:`bun 1.2.21`}};const bunSecurityScanner=defineRule({bindings:{bun:bunScannerBinding},description:`Bun supports a Security Scanner API that intercepts new packages at install time (e.g. Socket Firewall).`,docs:`https://github.com/bodadotsh/npm-security-best-practices#preinstall-scanners`,id:`bun-security-scanner`,severity:`info`,title:`Enable a bun install-time security scanner`});const{yarnrc:yarnrc$7}=CONFIG_FILES;const checksumVerification=requireConfigKey({bindings:{yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#checksumBehavior`,documentedDefault:`throw`,defaultSafety:`unconditional`,file:yarnrc$7,keyPath:[`checksumBehavior`],message:"Set `checksumBehavior: throw` in .yarnrc.yml to reject packages whose checksum does not match the lockfile.",value:`throw`}},description:`Throw on checksum mismatches so tampered or corrupted packages are never silently installed.`,docs:`https://yarnpkg.com/configuration/yarnrc#checksumBehavior`,id:`checksum-verification`,severity:`warn`,title:`Verify package checksums`});const PM_SIGNALS={aube:{configs:[CONFIG_FILES.aubeWorkspace.path],lockfiles:[`aube-lock.yaml`],reusesLockfiles:[`pnpm-lock.yaml`,`package-lock.json`,`npm-shrinkwrap.json`,`yarn.lock`,`bun.lock`]},bun:{configs:[CONFIG_FILES.bunfig.path],lockfiles:[`bun.lock`,`bun.lockb`]},deno:{configs:[CONFIG_FILES.denoJson.path,`deno.jsonc`],lockfiles:[`deno.lock`]},npm:{configs:[],lockfiles:[`package-lock.json`,`npm-shrinkwrap.json`]},pnpm:{configs:[CONFIG_FILES.pnpmWorkspace.path],lockfiles:[`pnpm-lock.yaml`]},yarn:{configs:[CONFIG_FILES.yarnrc.path],lockfiles:[`yarn.lock`]}};const LOCKFILE_DOCS={aube:`https://github.com/aubepkg/aube/blob/main/docs/package-manager/lockfiles.md`,bun:`https://bun.com/docs/install/lockfile`,deno:`https://docs.deno.com/runtime/reference/deno_json/#lockfile`,npm:`https://docs.npmjs.com/cli/v12/configuring-npm/package-lock-json`,pnpm:`https://pnpm.io/git#lockfiles`,yarn:`https://yarnpkg.com/getting-started/qa#should-lockfiles-be-committed-to-the-repository`};const LOCKFILE_VERSION_NOTES={deno:{configAvailableSince:`deno 1.28.0`}};const checkAubeBinaryLock=ctx=>{if(!ctx.exists(asRelPath(`bun.lockb`))||ctx.exists(asRelPath(`bun.lock`)))return void 0;return{state:`violation`,message:`Aube cannot read bun.lockb, even when another lockfile exists.`,remediation:{kind:`manual`,steps:["Run `bun install --save-text-lockfile` to generate bun.lock, then commit it."]}}};const checkNpmLockfile=(ctx,preferredLockfile)=>{if(ctx.exists(asRelPath(preferredLockfile)))return{state:`ok`};if(!ctx.exists(asRelPath(`npm-shrinkwrap.json`)))return void 0;if(ctx.pmVersion&&lt(ctx.pmVersion,`12.0.0`))return{state:`ok`};return{state:`violation`,message:ctx.pmVersion?`npm ${ctx.pmVersion} no longer reads npm-shrinkwrap.json. Use package-lock.json.`:`Found npm-shrinkwrap.json, but the npm target version is unknown; npm 12 no longer reads it.`,remediation:{kind:`manual`,steps:[`Verify and declare the npm version used by this project. For npm 12 or newer, migrate to package-lock.json and review the resulting dependency resolutions.`]}}};const lockfileBinding=pm=>{const{lockfiles,reusesLockfiles}=PM_SIGNALS[pm];const[preferredLockfile]=lockfiles;const acceptedLockfiles=[...lockfiles,...reusesLockfiles??[]];return{check(ctx){if(pm===`aube`){const binaryLock=checkAubeBinaryLock(ctx);if(binaryLock)return binaryLock}if(pm===`npm`){const npmLockfile=checkNpmLockfile(ctx,preferredLockfile);if(npmLockfile)return npmLockfile}else if(acceptedLockfiles.some(lockfile=>ctx.exists(asRelPath(lockfile))))return{state:`ok`};return{remediation:{kind:`manual`,steps:[`Install dependencies to generate ${preferredLockfile}, then commit it.`]},message:`No lockfile found. Generate and commit ${preferredLockfile}.`,state:`violation`}},docs:LOCKFILE_DOCS[pm],versionNote:LOCKFILE_VERSION_NOTES[pm]}};const isValidDenoLockSetting=lock=>{if(lock==null||typeof lock===`boolean`||typeof lock===`string`)return true;if(!isPlainRecord(lock))return false;return(lock.path==null||typeof lock.path===`string`)&&(lock.frozen==null||typeof lock.frozen===`boolean`)};const denoBinding$1={file:CONFIG_FILES.denoJson,docs:LOCKFILE_DOCS.deno,versionNote:LOCKFILE_VERSION_NOTES.deno,check(ctx,config){const lock=getByPath(config,[`lock`]);if(!isValidDenoLockSetting(lock))return{state:`violation`,message:`Invalid Deno lock setting.`,remediation:{kind:`manual`,steps:[`Use a boolean, a lockfile path, or an object with optional path and frozen fields.`]}};const selected=typeof lock===`string`?lock:getByPath(config,[`lock`,`path`]);const lockPath=selected==null?asRelPath(`deno.lock`):selected;if(lock!==false&&isRelPath(lockPath)&&ctx.exists(lockPath))return{state:`ok`};return{state:`violation`,message:lock===false?"Enable the Deno lockfile; `lock: false` disables it even when deno.lock exists.":`No readable project lockfile found at ${String(lockPath)}.`,remediation:{kind:`manual`,steps:[`Enable lockfile use in deno.json and generate and commit the configured lockfile within the project.`]}}}};const commitLockfile=defineRule({bindings:{aube:lockfileBinding(`aube`),bun:lockfileBinding(`bun`),deno:denoBinding$1,npm:lockfileBinding(`npm`),pnpm:lockfileBinding(`pnpm`),yarn:lockfileBinding(`yarn`)},description:"Lockfiles pin the full dependency tree and integrity hashes, enabling reproducible, verifiable installs (e.g. `npm ci`).",docs:`https://github.com/bodadotsh/npm-security-best-practices#3-include-lockfiles`,id:`commit-lockfile`,severity:`error`,title:`Commit a lockfile`});const{aubeWorkspace:aubeWorkspace$6,pnpmWorkspace:pnpmWorkspace$9}=CONFIG_FILES;const checkOverrides=(config,file)=>{const value=getByPath(config,[`overrides`]);if(!isNonEmptyObject(value))return{state:`ok`};return{actual:value,message:`Review \`overrides\` in ${file}. Overrides can replace transitive dependencies with arbitrary versions or forks.`,state:`violation`,remediation:{kind:`manual`,steps:["Review each entry in `overrides` — verify pinned versions address a known CVE and are not redirecting to untrusted packages."]}}};const makeBinding=(file,docs)=>({check(_ctx,config){return checkOverrides(config,file.path)},docs,file});const dependencyOverrides=defineRule({bindings:{aube:makeBinding(aubeWorkspace$6,`https://aube.sh/settings/`),pnpm:makeBinding(pnpmWorkspace$9,`https://pnpm.io/settings/dependency-resolution#overrides`)},description:`Flag dependency overrides that can replace transitive packages with arbitrary versions or forks — a supply-chain injection vector.`,docs:`https://pnpm.io/settings/dependency-resolution#overrides`,id:`dependency-overrides`,severity:`info`,title:`Review dependency overrides`});const{npmrc:npmrc$5,pnpmWorkspace:pnpmWorkspace$8,yarnrc:yarnrc$6,aubeWorkspace:aubeWorkspace$5,bunfig:bunfig$3}=CONFIG_FILES;const pnpmStrictDepBuildsDocs=`https://pnpm.io/settings/build#strictdepbuilds`;const pnpmVersionNote={configAvailableSince:`pnpm 10.3.0`,note:`pnpm-workspace.yaml settings require pnpm 10.6.0`,defaultSafeSince:`pnpm 11.0.0`};const checkPnpmBypass=(pmVersion,strictDepBuilds)=>{if(settingSupportedByTarget(`pnpm`,pmVersion,{file:pnpmWorkspace$8,keyPath:[`dangerouslyAllowAllBuilds`]})===false){const steps=["Remove `dangerouslyAllowAllBuilds: true` before upgrading to pnpm 10.9.0 or newer; the declared target ignores it, but a later version would activate the bypass."];if(strictDepBuilds!==true)steps.push("Set `strictDepBuilds: true` in pnpm-workspace.yaml to enable lifecycle-script gating for the declared target.");const proposal={kind:`manual`,steps};return{state:`violation`,actual:true,expected:false,message:`The declared target ignores this future-version bypass. Removing the ignored bypass alone does not provide protection against lifecycle scripts, and leaving it configured would disable gating after an upgrade.`,remediation:strictDepBuilds===true?proposal:guardRemediationAvailability(`pnpm`,pmVersion,proposal,[{file:pnpmWorkspace$8,keyPath:[`strictDepBuilds`]}])}}return{state:`violation`,actual:true,expected:false,message:"`dangerouslyAllowAllBuilds: true` in pnpm-workspace.yaml bypasses strictDepBuilds — remove it (or set it to false) to restore lifecycle-script gating.",remediation:{kind:`manual`,steps:["Remove `dangerouslyAllowAllBuilds: true` from pnpm-workspace.yaml (or set it to `false`). Setting `strictDepBuilds: true` alone has no effect while the bypass remains."]}}};const pnpmBinding$4={check(ctx,config){if(getByPath(config,[`ignoreScripts`])===true)return{state:`ok`};const dangerouslyAllowAllBuilds=getByPath(config,[`dangerouslyAllowAllBuilds`]);const strictDepBuilds=getByPath(config,[`strictDepBuilds`]);if(dangerouslyAllowAllBuilds===true)return checkPnpmBypass(ctx.pmVersion,strictDepBuilds);if(strictDepBuilds===true)return{state:`ok`};return{state:`violation`,actual:strictDepBuilds,expected:true,message:strictDepBuilds===void 0?"Set `strictDepBuilds: true` in pnpm-workspace.yaml to pin lifecycle-script gating across versions.":"Set `strictDepBuilds: true` in pnpm-workspace.yaml to block silent skips of un-approved dep builds.",remediation:guardRemediationAvailability(`pnpm`,ctx.pmVersion,proposeChanges(config,[{file:pnpmWorkspace$8,keyPath:[`strictDepBuilds`],op:`setKey`,value:true}]),[{file:pnpmWorkspace$8,keyPath:[`strictDepBuilds`]}])}},docs:pnpmStrictDepBuildsDocs,file:pnpmWorkspace$8,versionNote:pnpmVersionNote};const aubeBinding$2={check(ctx,config){const npmConfig=ctx.readConfig(npmrc$5);const jailBuilds=getByPath(config,[`jailBuilds`]);const strictDepBuilds=getByPath(npmConfig,[`strictDepBuilds`]);const violations=[];if(jailBuilds!==true)violations.push({state:`violation`,file:aubeWorkspace$5.path,actual:jailBuilds,expected:true,message:"Set `jailBuilds: true` in aube-workspace.yaml to sandbox approved builds.",remediation:proposeChanges(config,[{file:aubeWorkspace$5,keyPath:[`jailBuilds`],op:`setKey`,value:true}])});if(strictDepBuilds!==true)violations.push({state:`violation`,file:npmrc$5.path,actual:strictDepBuilds,expected:true,message:"Set `strictDepBuilds=true` in .npmrc to reject unreviewed lifecycle scripts.",remediation:proposeChanges(npmConfig,[{file:npmrc$5,keyPath:[`strictDepBuilds`],op:`setKey`,value:true}])});const[first,...rest]=violations;if(!first)return{state:`ok`};if(rest.length===0)return first;return{state:`violations`,violations:[first,...rest]}},docs:`https://aube.jdx.dev/security.html`,file:aubeWorkspace$5};const bunMessage='Set `ignoreScripts = true` under [install] in bunfig.toml — or set `"trustedDependencies": []` in package.json — to opt out of the curated allow-list (postinstall is already blocked for untrusted packages by default).';const bunBinding={check(ctx,config){const ignoreScripts=getByPath(config,[`install`,`ignoreScripts`]);if(ignoreScripts===true)return{state:`ok`};const trustedDependencies=ctx.packageJson?.trustedDependencies;if(trustedDependencies!==void 0&&trustedDependencies.length===0)return{state:`ok`};return{remediation:proposeChanges(config,[{file:bunfig$3,keyPath:[`install`,`ignoreScripts`],op:`setKey`,value:true}]),actual:ignoreScripts,expected:true,message:bunMessage,state:`violation`}},docs:`https://bun.com/docs/pm/lifecycle`,file:bunfig$3,severity:`info`,versionNote:{configAvailableSince:`bun 1.2.0`}};const builtRule$4=requireConfigKey({bindings:{npm:{docs:`https://docs.npmjs.com/cli/v11/using-npm/config#ignore-scripts`,file:npmrc$5,keyPath:[`ignore-scripts`],message:"Set `ignore-scripts=true` in .npmrc to block dependency lifecycle scripts.",value:true},yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#enableScripts`,documentedDefault:false,defaultSafety:`conditional`,file:yarnrc$6,keyPath:[`enableScripts`],message:"Set `enableScripts: false` in .yarnrc.yml to pin the policy across versions.",value:false,versionNote:{configAvailableSince:`yarn 2.0.0`,defaultSafeSince:`yarn 4.14.0`}}},description:`Malicious postinstall scripts are a primary supply-chain attack vector. Prevent automatic execution of dependency lifecycle scripts.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#1-disable-lifecycle-scripts`,id:`disable-lifecycle-scripts`,severity:`error`,title:`Disable dependency lifecycle scripts`});const disableLifecycleScripts=withAubeParanoid(overrideBindings(builtRule$4,{aube:aubeBinding$2,bun:bunBinding,pnpm:pnpmBinding$4}));const{npmrc:npmrc$4,yarnrc:yarnrc$5}=CONFIG_FILES;const yarnMessage="Set `enableStrictSsl: true` in .yarnrc.yml to enforce SSL certificate validation for registry connections.";const yarnBinding={check(_ctx,config){const strictSsl=getByPath(config,[`enableStrictSsl`]);const httpExceptions=getByPath(config,[`unsafeHttpWhitelist`]);if(Array.isArray(httpExceptions)&&httpExceptions.length>0)return{actual:httpExceptions,expected:``,remediation:{kind:`manual`,steps:["Review and remove entries from `unsafeHttpWhitelist` in .yarnrc.yml. Each entry permits unencrypted HTTP to that hostname. Also set `enableStrictSsl: true`."]},message:"`unsafeHttpWhitelist` in .yarnrc.yml allows unencrypted HTTP connections — remove entries or clear the list to enforce HTTPS-only registry access.",state:`violation`};if(strictSsl===true)return{state:`ok`};return{state:`violation`,actual:strictSsl,expected:true,message:yarnMessage,...strictSsl===void 0?{severity:`info`}:{},remediation:proposeChanges(config,[{file:yarnrc$5,keyPath:[`enableStrictSsl`],op:`setKey`,value:true}])}},docs:`https://yarnpkg.com/configuration/yarnrc#enableStrictSsl`,file:yarnrc$5};const builtRule$3=requireConfigKey({bindings:{npm:{docs:`https://docs.npmjs.com/cli/v11/using-npm/config#strict-ssl`,documentedDefault:true,defaultSafety:`unconditional`,file:npmrc$4,keyPath:[`strict-ssl`],message:"Set `strict-ssl=true` in .npmrc to enforce SSL certificate validation.",value:true}},description:`Require SSL certificate validation so registry traffic cannot be intercepted or tampered with.`,docs:`https://docs.npmjs.com/cli/v11/using-npm/config#strict-ssl`,id:`enforce-strict-ssl`,severity:`warn`,title:`Enforce SSL for registry connections`});const enforceStrictSsl=overrideBindings(builtRule$3,{yarn:yarnBinding});const isPublishableName=value=>typeof value===`string`&&value.trim().length>0;const resolveProjectType=(selected,inferredPackage)=>selected??(inferredPackage?`package`:`application`);const resolvePackageJsonProjectType=ctx=>{const pkg=ctx.packageJson;const inferredPackage=pkg?.private!==true&&isPublishableName(pkg?.name);return resolveProjectType(ctx.projectType,inferredPackage)};const resolveDenoProjectType=(ctx,denoConfig)=>resolveProjectType(ctx.projectType,getByPath(denoConfig,[`publish`])!==false&&isPublishableName(getByPath(denoConfig,[`name`])));const isPublishable=ctx=>resolvePackageJsonProjectType(ctx)===`package`;const{packageJson:packageJson$1,denoJson:denoJson$3}=CONFIG_FILES;const packageJsonFilesBinding={check(ctx){if(!isPublishable(ctx))return{state:`na`};const files=ctx.packageJson?.files;if(isNonBlankStringArray(files)&&files.length>0)return{state:`ok`};return{remediation:{kind:`manual`,steps:['Add a `files` array to package.json (e.g. ["dist"]) and verify with `npm pack --dry-run`.']},actual:files,message:"Add a `files` allow-list to package.json to limit what gets published.",state:`violation`}},docs:`https://docs.npmjs.com/cli/v11/configuring-npm/package-json#files`,file:packageJson$1};const denoPublishBinding={check(ctx,config){if(resolveDenoProjectType(ctx,config)!==`package`)return{state:`na`};const include=getByPath(config,[`publish`,`include`]);if(isNonBlankStringArray(include)&&include.length>0)return{state:`ok`};return{remediation:{kind:`manual`,steps:["Add a non-empty `publish.include` array to deno.json; use `publish.exclude` for additional exclusions."]},actual:include,message:"Add `publish.include` to deno.json to limit what gets published.",state:`violation`}},docs:`https://docs.deno.com/runtime/reference/deno_json/#publish---override-.gitignore`,file:denoJson$3};const filesField=defineRule({bindings:{aube:packageJsonFilesBinding,bun:packageJsonFilesBinding,deno:denoPublishBinding,npm:packageJsonFilesBinding,pnpm:packageJsonFilesBinding,yarn:packageJsonFilesBinding},description:"An explicit `files` array in package.json restricts what gets published, preventing accidental inclusion of secrets or local files.",docs:`https://github.com/bodadotsh/npm-security-best-practices#12-review-published-files`,id:`files-field`,projectTypes:[`package`],severity:`info`,title:`Declare a published files allow-list`});const{pnpmWorkspace:pnpmWorkspace$7,yarnrc:yarnrc$4,bunfig:bunfig$2,denoJson:denoJson$2}=CONFIG_FILES;const aubeFrozenCommand="Use `aube ci` or `aube install --frozen-lockfile` to fail when the lockfile would change.";const aubeBinding$1={check(){return{remediation:{kind:`manual`,steps:[aubeFrozenCommand]},message:"`preferFrozenLockfile` only reuses an up-to-date lockfile; it does not prevent updates. Verify that install commands enforce a frozen lockfile.",state:`violation`}},docs:`https://github.com/aubepkg/aube/blob/main/docs/cli/ci.md`,severity:`info`};const builtRule$2=requireConfigKey({bindings:{deno:{docs:`https://docs.deno.com/runtime/reference/deno_json/#lockfile`,file:denoJson$2,keyPath:[`lock`,`frozen`],message:"Set `lock.frozen: true` in deno.json for reproducible, verified installs.",value:true},bun:{docs:`https://bun.com/docs/runtime/bunfig#install-frozenlockfile`,file:bunfig$2,keyPath:[`install`,`frozenLockfile`],message:"Set `frozenLockfile = true` under [install] in bunfig.toml so installs fail when the lockfile would change.",value:true,versionNote:{configAvailableSince:`bun 0.6.10`}},pnpm:{docs:`https://pnpm.io/cli/install#--frozen-lockfile`,file:pnpmWorkspace$7,keyPath:[`frozenLockfile`],message:`pnpm defaults frozenLockfile to true in CI — set it explicitly in pnpm-workspace.yaml to pin the policy outside CI too.`,value:true},yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#enableImmutableInstalls`,documentedDefault:true,defaultSafety:`conditional`,file:yarnrc$4,keyPath:[`enableImmutableInstalls`],message:"Set `enableImmutableInstalls: true` in .yarnrc.yml to pin the policy outside CI too.",value:true,versionNote:{configAvailableSince:`yarn 2.0.0`,defaultSafeSince:`yarn 3.0.0 in CI`}}},description:`Refuse to mutate the lockfile on install so unexpected dependency changes fail loudly.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#3-include-lockfiles`,id:`frozen-lockfile`,severity:`warn`,title:`Freeze the lockfile`});const frozenLockfile=overrideBindings(builtRule$2,{aube:aubeBinding$1});const{pnpmWorkspace:pnpmWorkspace$6}=CONFIG_FILES;const pnpmBinding$3={check(ctx,config){const value=getByPath(config,[`frozenStore`]);if(value===true)return{state:`ok`};return{remediation:guardRemediationAvailability(`pnpm`,ctx.pmVersion,{kind:`manual`,steps:["Populate the store before enabling `frozenStore: true`. Use it for read-only deployments; it is incompatible with --force and a configured pnpr server."]},[{file:pnpmWorkspace$6,keyPath:[`frozenStore`]}]),actual:value,message:"For a pre-populated read-only store, consider `frozenStore` in pnpm-workspace.yaml. Normal installs need a writable store.",state:`violation`}},docs:`https://pnpm.io/settings/store#frozenstore`,file:pnpmWorkspace$6,versionNote:{configAvailableSince:`pnpm 11.7.0`}};const frozenStore=defineRule({bindings:{pnpm:pnpmBinding$3},description:`Consider read-only store access for deployments whose dependencies are already present.`,docs:`https://pnpm.io/settings/store#frozenstore`,id:`frozen-store`,severity:`info`,title:`Consider a pre-populated read-only store`});const{yarnrc:yarnrc$3}=CONFIG_FILES;const hardenedMode=requireConfigKey({bindings:{yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#enableHardenedMode`,documentedDefault:true,defaultSafety:`conditional`,file:yarnrc$3,keyPath:[`enableHardenedMode`],message:"yarn enables hardened mode automatically only for pull requests on public repositories — set `enableHardenedMode: true` in .yarnrc.yml to extend end-to-end install verification to every install.",value:true,versionNote:{configAvailableSince:`yarn 4.0.0`,defaultSafeSince:`yarn 4.0.0 (conditional: auto-enabled for PRs on public repositories)`}}},description:`Yarn 4's enableHardenedMode performs end-to-end checksum, lockfile, and version verification at install time.`,docs:`https://yarnpkg.com/configuration/yarnrc#enableHardenedMode`,id:`hardened-mode`,severity:`warn`,title:`Enable hardened mode (yarn)`});const DENO_DURATION=/^\+?P(?:\+?\d+[Ww]|(?:\d+[Dd])*(?:T(?:\d+[HhMm]|\d+(?:\.\d+)?[Ss])+)?)$/u;const DENO_MIN_TIMESTAMP_MS=Date.UTC(-262143,0,1);const DENO_UNIT_SECONDS={w:604800,d:86400,h:3600,m:60,s:1};const DENO_DATE=/^\d{4}-\d{2}-\d{2}$/u;const DENO_TIMESTAMP=/^\d{4}-\d{2}-\d{2}[Tt ](?:[01]\d|2[0-3]):[0-5]\d:(?:[0-5]\d|60)(?:\.\d+)?(?:[Zz]|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/u;const DENO_OFFSET_TIMESTAMP=/^\d{4}-\d{2}-\d{2}T(?:[01]\d|2[0-3]):[0-5]\d(?::(?:[0-5]\d|60))?[+-](?:[01]\d|2[0-3]):?[0-5]\d$/u;const DENO_DURATION_TOKEN=/(?<integer>\d+)(?:\.(?<fraction>\d+))?(?<unit>[WDHMS])/giu;const LEAP_SECOND=/:60(?=\.|Z|z|[+-])/u;const isActiveDurationSeconds=(seconds,nowMs)=>seconds>0&&Number.isFinite(seconds)&&nowMs-seconds*1e3>=DENO_MIN_TIMESTAMP_MS;const durationSeconds=value=>{let seconds=0;for(const token of value.matchAll(DENO_DURATION_TOKEN)){const{integer,fraction,unit}=token.groups??{};const fractionDigits=(fraction??``).slice(0,9)||`0`;const amount=Number(integer)+Number(`0.${fractionDigits}`);const secondsPerUnit=DENO_UNIT_SECONDS[unit?.toLowerCase()??``]??0;seconds+=amount*secondsPerUnit}return seconds};const isActiveDenoReleaseAge=(value,nowMs,parse)=>{if(typeof value===`number`)return Number.isSafeInteger(value)&&isActiveDurationSeconds(value*60,nowMs);if(typeof value!==`string`)return false;if(/^\d+$/u.test(value)){const minutes=Number(value);return Number.isSafeInteger(minutes)&&isActiveDurationSeconds(minutes*60,nowMs)}if(DENO_DURATION.test(value))return isActiveDurationSeconds(durationSeconds(value),nowMs);if(!DENO_DATE.test(value)&&!DENO_TIMESTAMP.test(value)&&!DENO_OFFSET_TIMESTAMP.test(value))return false;const calendarDate=value.slice(0,10);const midnight=new Date(`${calendarDate}T00:00:00Z`);const hasLeapSecond=LEAP_SECOND.test(value);const timestampMs=parse(value.replace(LEAP_SECOND,`:59`))+(hasLeapSecond?1e3:0);return!Number.isNaN(midnight.valueOf())&&midnight.toISOString().slice(0,10)===calendarDate&&timestampMs<nowMs};const HOURS_PER_DAY=24;const MINUTES_PER_HOUR=60;const SECONDS_PER_MINUTE=60;const MINUTES_PER_DAY=HOURS_PER_DAY*MINUTES_PER_HOUR;const SECONDS_PER_DAY=MINUTES_PER_DAY*SECONDS_PER_MINUTE;const RECOMMENDED_RELEASE_AGE_DAYS=3;const RECOMMENDED_RELEASE_AGE_MINUTES=RECOMMENDED_RELEASE_AGE_DAYS*MINUTES_PER_DAY;const RECOMMENDED_RELEASE_AGE_SECONDS=RECOMMENDED_RELEASE_AGE_DAYS*SECONDS_PER_DAY;const DOCUMENTED_DEFAULT_MINUTES=MINUTES_PER_DAY;const{npmrc:npmrc$3,pnpmWorkspace:pnpmWorkspace$5,yarnrc:yarnrc$2,bunfig:bunfig$1,denoJson:denoJson$1,aubeWorkspace:aubeWorkspace$4}=CONFIG_FILES;const isPositiveNumber=value=>typeof value===`number`&&Number.isFinite(value)&&value>0;const isPositiveYarnDuration=value=>{if(isPositiveNumber(value))return true;if(typeof value!==`string`||!/^\d*\.?\d+(?:ms|s|m|h|d|w)?$/u.test(value))return false;const durationAmount=Number.parseFloat(value);return Number.isFinite(durationAmount)&&durationAmount>0};const isActiveDenoNpmrcAge=(value,nowMs,parse)=>typeof value===`number`&&Number.isSafeInteger(value)&&isActiveDenoReleaseAge(value*MINUTES_PER_DAY,nowMs,parse);const baseRule$2=requireConfigKey({bindings:{aube:{accept:isPositiveNumber,docs:`https://aube.sh/settings/`,documentedDefault:DOCUMENTED_DEFAULT_MINUTES,defaultSafety:`unconditional`,file:aubeWorkspace$4,keyPath:[`minimumReleaseAge`],message:`Set minimumReleaseAge (~${RECOMMENDED_RELEASE_AGE_MINUTES} minutes for a 3-day cooldown) in aube-workspace.yaml.`,value:RECOMMENDED_RELEASE_AGE_MINUTES},bun:{accept:isPositiveNumber,docs:`https://bun.com/docs/runtime/bunfig#install-minimumreleaseage`,file:bunfig$1,keyPath:[`install`,`minimumReleaseAge`],message:`Set install.minimumReleaseAge (bun) to ~${RECOMMENDED_RELEASE_AGE_SECONDS} seconds to quarantine brand-new releases.`,value:RECOMMENDED_RELEASE_AGE_SECONDS,versionNote:{configAvailableSince:`bun 1.3.0`}},pnpm:{accept:isPositiveNumber,docs:`https://pnpm.io/settings/dependency-resolution#minimumreleaseage`,documentedDefault:DOCUMENTED_DEFAULT_MINUTES,defaultSafety:`conditional`,file:pnpmWorkspace$5,keyPath:[`minimumReleaseAge`],message:`Set minimumReleaseAge (~${RECOMMENDED_RELEASE_AGE_MINUTES} minutes for a 3-day cooldown) in pnpm-workspace.yaml.`,value:RECOMMENDED_RELEASE_AGE_MINUTES,versionNote:{configAvailableSince:`pnpm 10.16.0`,defaultSafeSince:`pnpm 11.0.0 (1440 minutes)`}},yarn:{accept:isPositiveYarnDuration,docs:`https://yarnpkg.com/configuration/yarnrc#npmMinimalAgeGate`,documentedDefault:DOCUMENTED_DEFAULT_MINUTES,defaultSafety:`conditional`,file:yarnrc$2,keyPath:[`npmMinimalAgeGate`],message:`Set npmMinimalAgeGate (~${RECOMMENDED_RELEASE_AGE_MINUTES} minutes for a 3-day cooldown) in .yarnrc.yml.`,value:RECOMMENDED_RELEASE_AGE_MINUTES,versionNote:{configAvailableSince:`yarn 4.10.0`,defaultSafeSince:`yarn 4.15.0 (1440 minutes)`}}},description:`Refuse to install releases newer than a cooldown window so freshly published (possibly compromised) versions are skipped.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#2-set-cooldowns--minimum-release-age`,id:`minimum-release-age`,severity:`warn`,title:`Set a minimum release age`});const checkNpmBefore=(actual,nowMs,time,ctx)=>{if((typeof actual===`string`||typeof actual===`number`)&&time.parse(String(actual))<nowMs)return{state:`ok`};return{state:`violation`,actual,expected:`a date in the past`,message:`Use a valid past before cutoff, or remove before and set min-release-age.`,remediation:{kind:`manual`,steps:[`In .npmrc, set before to a valid past date. A future or disabled before overrides min-release-age in this file.`,...guardRemediationAvailability(`npm`,ctx.pmVersion,{kind:`manual`,steps:[`Alternatively, remove before and set min-release-age to ~${RECOMMENDED_RELEASE_AGE_DAYS} days.`]},[{file:npmrc$3,keyPath:[`min-release-age`]}])?.steps??[]]}}};const createNpmBinding=time=>({file:npmrc$3,docs:`https://docs.npmjs.com/cli/v12/using-npm/config#min-release-age`,versionNote:{note:`min-release-age available since npm 11.10.0`},check(ctx,config){const nowMs=time.now();if(Object.hasOwn(config,`before`))return checkNpmBefore(config.before,nowMs,time,ctx);const actual=getByPath(config,[`min-release-age`]);const ageDays=typeof actual===`number`||typeof actual===`string`?Number(actual):NaN;const cutoffMs=new Date(nowMs-SECONDS_PER_DAY*1e3*ageDays).valueOf();if(ageDays>0&&Number.isFinite(cutoffMs)&&cutoffMs<nowMs)return{state:`ok`};return{state:`violation`,actual,expected:RECOMMENDED_RELEASE_AGE_DAYS,message:`Set min-release-age to ~${RECOMMENDED_RELEASE_AGE_DAYS} days to quarantine brand-new releases.`,remediation:guardRemediationAvailability(`npm`,ctx.pmVersion,proposeChanges(config,[{file:npmrc$3,op:`setKey`,keyPath:[`min-release-age`],value:RECOMMENDED_RELEASE_AGE_DAYS}]),[{file:npmrc$3,keyPath:[`min-release-age`]}])}}});const denoAgeViolation=actual=>({state:`violation`,actual,expected:`P3D`,message:`Set minimumDependencyAge (e.g. "P3D" for a ${RECOMMENDED_RELEASE_AGE_DAYS}-day cooldown) in deno.json.`});const createDenoBinding=time=>({file:denoJson$1,docs:`https://docs.deno.com/runtime/reference/deno_json/`,versionNote:{defaultSafeSince:`deno 2.9.0 (1440 minutes)`,note:`object age may be omitted; project .npmrc fallback available since deno 2.8.1`},check(ctx,config){const actual=getByPath(config,[`minimumDependencyAge`]);const nowMs=time.now();const isAgeObject=isPlainRecord(actual);if(isAgeObject&&(Object.keys(actual).some(key=>key!==`age`&&key!==`exclude`)||actual.exclude!==void 0&&!isNonBlankStringArray(actual.exclude)))return{...denoAgeViolation(actual),remediation:{kind:`manual`,steps:[`Use only age and exclude in minimumDependencyAge. Make exclude a list of package names and set age to a positive supported duration.`]}};const age=isAgeObject?actual.age:actual;if(age==null){const npmrcConfig=ctx.readConfig(npmrc$3);const npmrcAge=getByPath(npmrcConfig,[`min-release-age`]);if(isActiveDenoNpmrcAge(npmrcAge,nowMs,time.parse))return{state:`ok`};if(npmrcAge===0)return{state:`violation`,actual:npmrcAge,expected:RECOMMENDED_RELEASE_AGE_DAYS,file:npmrc$3.path,message:`Set min-release-age to ~${RECOMMENDED_RELEASE_AGE_DAYS} days in .npmrc, or set minimumDependencyAge in deno.json.`,remediation:proposeChanges(npmrcConfig,[{file:npmrc$3,op:`setKey`,keyPath:[`min-release-age`],value:RECOMMENDED_RELEASE_AGE_DAYS}])}}if(isActiveDenoReleaseAge(age,nowMs,time.parse))return{state:`ok`};return{...denoAgeViolation(actual),remediation:proposeChanges(config,[{file:denoJson$1,op:`setKey`,keyPath:isAgeObject?[`minimumDependencyAge`,`age`]:[`minimumDependencyAge`],value:`P3D`}])}}});const createMinimumReleaseAge=time=>overrideBindings(baseRule$2,{npm:createNpmBinding(time),deno:createDenoBinding(time)});const{pnpmWorkspace:pnpmWorkspace$4}=CONFIG_FILES;const pnpmBinding$2={check(_ctx,config){const value=getByPath(config,[`namedRegistries`]);if(!isNonEmptyObject(value))return{state:`ok`};return{remediation:{kind:`manual`,steps:[`Verify each named registry points to a trusted source and that scoped packages cannot be hijacked via a public registry of the same name.`]},actual:value,message:"Review `namedRegistries` in pnpm-workspace.yaml. Custom registry mappings can redirect package resolution away from the public registry, enabling dependency confusion attacks.",state:`violation`}},docs:`https://pnpm.io/settings/dependency-resolution#namedregistries`,file:pnpmWorkspace$4};const namedRegistries=defineRule({bindings:{pnpm:pnpmBinding$2},description:`Flag named registry mappings that redirect package resolution to custom registries, which may enable dependency confusion attacks.`,docs:`https://pnpm.io/settings/dependency-resolution#namedregistries`,id:`named-registries`,severity:`info`,title:`Review named registry mappings`});const{aubeWorkspace:aubeWorkspace$3}=CONFIG_FILES;const paranoidMode=requireConfigKey({bindings:{aube:{docs:`https://aube.jdx.dev/security.html`,file:aubeWorkspace$3,keyPath:[`paranoid`],message:"Set `paranoid: true` in aube-workspace.yaml to enable the strict-security bundle (trustPolicy, jailBuilds, minimumReleaseAgeStrict, strictStoreIntegrity, strictDepBuilds, advisoryCheck).",value:true}},description:`Activate the strict-security setting bundle that forces trustPolicy, jailBuilds, minimumReleaseAgeStrict, strictStoreIntegrity, strictDepBuilds, and advisoryCheck on in one switch.`,docs:`https://aube.jdx.dev/security.html`,id:`paranoid-mode`,severity:`info`,title:`Enable paranoid security mode`});const{pnpmWorkspace:pnpmWorkspace$3}=CONFIG_FILES;const pnpmBinding$1={check(_ctx,config){const value=getByPath(config,[`patchedDependencies`]);if(!isNonEmptyObject(value))return{state:`ok`};return{remediation:{kind:`manual`,steps:["Review each entry in `patchedDependencies` — verify patches address a known issue and have not been tampered with."]},actual:value,message:"Review `patchedDependencies` in pnpm-workspace.yaml. Local patches modify dependency code after the registry artifact is verified; review the patch files separately.",state:`violation`}},docs:`https://pnpm.io/cli/patch#patcheddependencies`,file:pnpmWorkspace$3};const patchedDependencies=defineRule({bindings:{pnpm:pnpmBinding$1},description:`Review local patches separately from the registry artifacts they modify.`,docs:`https://pnpm.io/cli/patch#patcheddependencies`,id:`patched-dependencies`,severity:`info`,title:`Review patched dependencies`});const{npmrc:npmrc$2,pnpmWorkspace:pnpmWorkspace$2,yarnrc:yarnrc$1,bunfig,denoJson}=CONFIG_FILES;const REGISTRY_VERSION=/^(?:npm|jsr):(?:@[^/@]+\/)?[^/@]+@(?<version>[^/]*)/u;const isUnpinnedRegistryImport=specifier=>{if(!specifier.startsWith(`npm:`)&&!specifier.startsWith(`jsr:`))return false;const version=REGISTRY_VERSION.exec(specifier)?.groups?.version;const normalized=version?.startsWith(`=`)?version.slice(1):version;return normalized===void 0||valid(normalized)===null};const collectUnpinnedImports=(imports,location)=>{if(!isPlainRecord(imports))throw new ConfigError(`deno.json: ${location} must be an import mapping object.`);const offenders=[];for(const[name,value]of Object.entries(imports)){if(value===null)continue;if(typeof value!==`string`)throw new ConfigError(`deno.json: ${location}.${name} must be a string or null.`);if(isUnpinnedRegistryImport(value))offenders.push(`${location}.${name}=${value}`)}return offenders};const MAX_SAMPLE_COUNT=3;const createUnpinnedImportsViolation=offenders=>{const sample=offenders.slice(0,MAX_SAMPLE_COUNT).join(`, `);const more=offenders.length>MAX_SAMPLE_COUNT?` (and ${offenders.length-MAX_SAMPLE_COUNT} more)`:``;return{remediation:{kind:`manual`,steps:["Pin each reported imports/scopes entry in deno.json to an exact version. For top-level imports, `deno add --save-exact <pkg>` can help; edit scoped mappings at their reported locations."]},message:`${offenders.length} deno imports are not pinned: ${sample}${more}. Use \`deno add --save-exact\` or pin manually.`,state:`violation`}};const collectUnpinnedInlineImports=config=>{const imports=getByPath(config,[`imports`]);const offenders=imports==null?[]:collectUnpinnedImports(imports,`imports`);const scopes=getByPath(config,[`scopes`]);if(scopes==null)return offenders;if(!isPlainRecord(scopes))throw new ConfigError(`deno.json: scopes must be an object.`);for(const[scope,mapping]of Object.entries(scopes))for(const offender of collectUnpinnedImports(mapping,`scopes[${JSON.stringify(scope)}]`))offenders.push(offender);return offenders};const denoBinding={check(_ctx,config){const offenders=collectUnpinnedInlineImports(config);if(offenders.length===0)return{state:`ok`};return createUnpinnedImportsViolation(offenders)},docs:`https://docs.deno.com/runtime/reference/cli/add/`,file:denoJson,versionNote:{configAvailableSince:`deno 1.30.0`}};const npmBinding$1={file:npmrc$2,docs:`https://docs.npmjs.com/cli/v12/using-npm/config#save-exact`,check(_ctx,config){const exact=getByPath(config,[`save-exact`]);const prefix=getByPath(config,[`save-prefix`]);if(exact===true||prefix===``||prefix===`=`)return{state:`ok`};return{remediation:proposeChanges(config,[{op:`setKey`,file:npmrc$2,keyPath:[`save-exact`],value:true}]),state:`violation`,actual:exact,expected:true,message:"Set `save-exact=true` in .npmrc to save exact versions by default."}}};const aubeBinding={file:npmrc$2,docs:`https://aube.sh/settings/#setting-saveprefix`,check(_ctx,config){const hasDashedPrefix=Object.hasOwn(config,`save-prefix`);const hasCamelPrefix=Object.hasOwn(config,`savePrefix`);const prefixesAreEmpty=(!hasDashedPrefix||config[`save-prefix`]===``)&&(!hasCamelPrefix||config.savePrefix===``);if((hasDashedPrefix||hasCamelPrefix)&&prefixesAreEmpty)return{state:`ok`};return{remediation:{kind:`manual`,steps:["Use one `save-prefix=` entry in .npmrc. Aube resolves aliases by line order; reconcile conflicting entries before changing them."]},state:`violation`,message:"Set `save-prefix=` in .npmrc; remove any conflicting `savePrefix` alias."}}};const baseRule$1=requireConfigKey({bindings:{bun:{docs:`https://bun.com/docs/runtime/bunfig#install-exact`,file:bunfig,keyPath:[`install`,`exact`],message:"Set `exact = true` under [install] in bunfig.toml to pin exact versions.",value:true,versionNote:{note:`install.exact verified in bun 1.2.0`}},pnpm:{accept:value=>value===``||value===`=`,docs:`https://pnpm.io/settings/other#saveprefix`,file:pnpmWorkspace$2,keyPath:[`savePrefix`],message:"Set `savePrefix: ''` in pnpm-workspace.yaml to pin exact versions.",value:``},yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#defaultSemverRangePrefix`,file:yarnrc$1,keyPath:[`defaultSemverRangePrefix`],message:"Set `defaultSemverRangePrefix: ''` in .yarnrc.yml to pin exact versions.",value:``,versionNote:{configAvailableSince:`yarn 2.0.0`}}},description:`Semver ranges (^, ~) auto-adopt new releases, including compromised ones. Save exact versions by default; for Deno, inspect registry mappings in both inline imports and scopes (not external import maps).`,docs:`https://github.com/bodadotsh/npm-security-best-practices#4-pin-dependency-versions`,id:`pin-exact-versions`,severity:`error`,title:`Pin exact dependency versions`});const pinExactVersions=overrideBindings(baseRule$1,{aube:aubeBinding,deno:denoBinding,npm:npmBinding$1});const{npmrc:npmrc$1,yarnrc}=CONFIG_FILES;const npmrcProvenance={file:npmrc$1,keyPath:[`provenance`],message:"Set `provenance=true` (and publish from CI) to attest releases.",value:true};const baseRule=requireConfigKey({applies:isPublishable,bindings:{bun:{...npmrcProvenance,docs:`https://github.com/oven-sh/bun/issues/15601`,message:"Set `provenance=true` in .npmrc and publish via `bunx npm publish` from CI — `bun publish` does not emit provenance attestations natively (tracking: oven-sh/bun#15601)."},npm:{...npmrcProvenance,docs:`https://docs.npmjs.com/cli/v11/using-npm/config#provenance`,versionNote:{configAvailableSince:`npm 9.5.0`}},pnpm:{...npmrcProvenance,docs:`https://pnpm.io/cli/publish`},yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#npmPublishProvenance`,file:yarnrc,keyPath:[`npmPublishProvenance`],message:"Set `npmPublishProvenance: true` (and publish from CI) to attest releases.",value:true}},description:`Provenance statements (via Sigstore) bind a published artifact to its recorded source and build.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#11-generate-provenance-statements`,id:`provenance`,projectTypes:[`package`],severity:`warn`,title:`Publish with provenance`});const npmBinding=baseRule.bindings.npm;if(npmBinding===void 0)throw new TypeError(`Provenance requires an npm binding.`);const provenance=overrideBindings(baseRule,{npm:{...npmBinding,check(ctx,config){if(!isPublishable(ctx))return{state:`na`};const publishConfig=ctx.packageJson?.publishConfig;if(!publishConfig||!Object.hasOwn(publishConfig,`provenance`))return npmBinding.check(ctx,config);if(publishConfig.provenance===true)return{state:`ok`};const file=CONFIG_FILES.packageJson;return{state:`violation`,file:file.path,actual:publishConfig.provenance,expected:true,message:`Set publishConfig.provenance=true in package.json (and publish from CI) to attest releases.`,remediation:guardRemediationAvailability(`npm`,ctx.pmVersion,proposeChanges(ctx.readConfig(file),[{file,op:`setKey`,keyPath:[`publishConfig`,`provenance`],value:true}]))}}}});const{packageJson}=CONFIG_FILES;const publishAccessBinding={check(ctx){if(!isPublishable(ctx))return{state:`na`};const access=ctx.packageJson?.publishConfig?.access;if(access===`public`||access===`restricted`)return{state:`ok`};return{remediation:{kind:`manual`,steps:['Add `"publishConfig": { "access": "public" }` (or `"restricted"`) to package.json so publishes never default unexpectedly.']},actual:access,message:"Set `publishConfig.access` in package.json to `public` or `restricted` to declare publish scope explicitly.",state:`violation`}},docs:`https://docs.npmjs.com/cli/v11/configuring-npm/package-json#publishconfig`,file:packageJson};const publishAccess=defineRule({bindings:{aube:publishAccessBinding,bun:publishAccessBinding,npm:{...publishAccessBinding,docs:`https://docs.npmjs.com/cli/v12/using-npm/config#access`,check(ctx,config){if(isPublishable(ctx)&&ctx.packageJson?.publishConfig?.access===`private`)return{state:`ok`};return publishAccessBinding.check(ctx,config)}},pnpm:publishAccessBinding,yarn:publishAccessBinding},description:"Set `publishConfig.access` so a misconfigured scope or registry never accidentally publishes an internal package publicly.",docs:`https://github.com/bodadotsh/npm-security-best-practices#for-maintainers`,id:`publish-access`,projectTypes:[`package`],severity:`info`,title:`Declare publish access explicitly`});const{pnpmWorkspace:pnpmWorkspace$1}=CONFIG_FILES;const pnpmBinding={check(_ctx,config){const value=getByPath(config,[`useRunningStoreServer`]);if(value!==true)return{state:`ok`};return{remediation:{kind:`manual`,steps:[`Verify the store server is running in a trusted environment and that its communication channel is not exposed to untrusted networks.`]},actual:value,message:"Review `useRunningStoreServer` in pnpm-workspace.yaml. Delegating store operations to an external process introduces a trust boundary — the server process can serve tampered packages.",state:`violation`}},docs:`https://pnpm.io/settings/store#userunningstoreserver`,file:pnpmWorkspace$1};const storeServer=defineRule({bindings:{pnpm:pnpmBinding},description:`Flag use of an external store server process, which introduces a trust boundary where tampered packages could be served.`,docs:`https://pnpm.io/settings/store#userunningstoreserver`,id:`store-server`,severity:`info`,title:`Review store server usage`});const{npmrc}=CONFIG_FILES;const strictAllowScripts=defineRule({bindings:{npm:{check(_ctx,config){if(getByPath(config,[`ignore-scripts`])===true)return{state:`ok`};const bypass=getByPath(config,[`dangerously-allow-all-scripts`]);if(bypass===true)return{actual:bypass,expected:false,remediation:{kind:`manual`,steps:["Remove `dangerously-allow-all-scripts=true` from .npmrc (or set it to false), then set `strict-allow-scripts=true`. The bypass overrides strict script approval."]},message:"`dangerously-allow-all-scripts=true` bypasses script approval even when `strict-allow-scripts=true`.",state:`violation`};const strictAllowScriptsSetting=getByPath(config,[`strict-allow-scripts`]);if(strictAllowScriptsSetting===true)return{state:`ok`};return{remediation:proposeChanges(config,[{file:npmrc,keyPath:[`strict-allow-scripts`],op:`setKey`,value:true}]),actual:strictAllowScriptsSetting,expected:true,message:"Set `strict-allow-scripts=true` in .npmrc to fail installation for unreviewed install scripts.",state:`violation`}},docs:`https://docs.npmjs.com/cli/v12/using-npm/config#strict-allow-scripts`,file:npmrc}},description:`Turn install-script policy warnings into hard errors so unapproved lifecycle scripts block installation.`,docs:`https://docs.npmjs.com/cli/v12/using-npm/config#strict-allow-scripts`,id:`strict-allow-scripts`,severity:`warn`,title:`Require explicit script allow-listing`});const{aubeWorkspace:aubeWorkspace$2}=CONFIG_FILES;const builtRule$1=requireConfigKey({bindings:{aube:{docs:`https://aube.jdx.dev/settings/`,file:aubeWorkspace$2,keyPath:[`minimumReleaseAgeStrict`],message:"Set `minimumReleaseAgeStrict: true` in aube-workspace.yaml to make the release age gate a hard block.",value:true}},description:`Fail when no satisfying version meets the release age, instead of falling back to the lowest satisfying version.`,docs:`https://aube.jdx.dev/security.html`,id:`strict-release-age`,severity:`info`,title:`Enforce strict release age gate`});const strictReleaseAge=withAubeParanoid(builtRule$1);const{aubeWorkspace:aubeWorkspace$1}=CONFIG_FILES;const strictStoreIntegrity=defineRule({bindings:{aube:{check(_ctx,config){if(getByPath(config,[`verifyStoreIntegrity`])===false)return{actual:false,expected:true,remediation:{kind:`manual`,steps:["Set `verifyStoreIntegrity: true` (or remove `verifyStoreIntegrity: false`) in aube-workspace.yaml, and enable `strictStoreIntegrity: true` or `paranoid: true`."]},message:"`verifyStoreIntegrity: false` bypasses strict integrity checks, including when `paranoid: true`.",state:`violation`};const strictStoreIntegritySetting=getByPath(config,[`strictStoreIntegrity`]);if(strictStoreIntegritySetting===true||getByPath(config,[`paranoid`])===true)return{state:`ok`};return{remediation:proposeChanges(config,[{file:aubeWorkspace$1,keyPath:[`strictStoreIntegrity`],op:`setKey`,value:true}]),actual:strictStoreIntegritySetting,expected:true,message:"Set `strictStoreIntegrity: true` in aube-workspace.yaml to refuse tarballs that lack integrity metadata.",state:`violation`}},docs:`https://aube.jdx.dev/settings/`,file:aubeWorkspace$1}},description:`Refuse to import tarballs from the registry when the packument lacks a dist.integrity field, preventing silent integrity bypass.`,docs:`https://aube.jdx.dev/security.html`,id:`strict-store-integrity`,severity:`warn`,title:`Require tarball integrity metadata`});const{aubeWorkspace,pnpmWorkspace}=CONFIG_FILES;const builtRule=requireConfigKey({bindings:{aube:{docs:`https://aube.jdx.dev/security.html`,documentedDefault:`no-downgrade`,defaultSafety:`unconditional`,file:aubeWorkspace,keyPath:[`trustPolicy`],message:"Set `trustPolicy: no-downgrade` in aube-workspace.yaml to block packages whose trust level has decreased.",value:`no-downgrade`},pnpm:{docs:`https://pnpm.io/settings/dependency-resolution#trustpolicy`,file:pnpmWorkspace,keyPath:[`trustPolicy`],message:"Set `trustPolicy: no-downgrade` in pnpm-workspace.yaml to block packages whose trust level has decreased.",value:`no-downgrade`,versionNote:{configAvailableSince:`pnpm 10.21.0`}}},description:`Fail installation when a package trust level has decreased compared to previous releases, catching publisher credential downgrades.`,docs:`https://pnpm.io/settings/dependency-resolution#trustpolicy`,id:`trust-policy`,severity:`warn`,title:`Enforce trust policy on dependency updates`});const trustPolicy=withAubeParanoid(builtRule);const bindingFor=(pm,includesFile)=>{const settings=settingAvailabilityByPM[pm].filter(setting=>includesFile(setting.file.path));return{check(ctx){const version=ctx.pmVersion;if(version===void 0)return{state:`na`};const unsupportedByFile=new Map;for(const setting of settings){if(!lt(version,setting.since)||getByPath(ctx.readConfig(setting.file),setting.keyPath)===void 0)continue;const fileSettings=unsupportedByFile.get(setting.file.path)??[];fileSettings.push(setting);unsupportedByFile.set(setting.file.path,fileSettings)}const[first,...rest]=Array.from(unsupportedByFile,([file,fileSettings])=>{const requirements=[];const steps=[`Verify the PM version used by your install or publish command. If the declared target is accurate, upgrade the PM or use a security control supported by that target; removing a setting alone does not provide its protection.`];for(const setting of fileSettings){requirements.push(`${setting.keyPath.join(`.`)} (requires ${pm} >=${setting.since})`);steps.push(`${setting.file.path}#${setting.keyPath.join(`.`)}: ${setting.source}`)}return{state:`violation`,file,message:`Target ${pm} ${version} predates support for ${file}: ${requirements.join(`; `)}.`,remediation:{kind:`manual`,steps}}});return first?{state:`violations`,violations:[first,...rest]}:{state:`ok`}}}};const createUnsupportedSettings=includesFile=>defineRule({id:`unsupported-settings`,title:`Use settings available in the target PM version`,description:`Report configured settings whose recorded introduction version is newer than the declared or explicit stable PM target. Reports each affected file separately, grouping its unsupported keys. Findings retain their evaluation directory. Only the coverage table below is checked. Unknown targets and unlisted settings are not evaluated for availability.`,severity:`error`,docs:`https://github.com/pHo9UBenaA/siro/blob/main/docs/rules.md#unsupported-settings--error`,bindings:{npm:bindingFor(`npm`,includesFile),pnpm:bindingFor(`pnpm`,includesFile),yarn:bindingFor(`yarn`,includesFile),bun:bindingFor(`bun`,includesFile),deno:bindingFor(`deno`,includesFile)}});const unsupportedSettings=createUnsupportedSettings(()=>true);const createBuiltinRules=time=>[advisoryCheck,approvedGitRepos,auditSuppression,blockAutoInstall,blockExoticSubdeps,bunSecurityScanner,checksumVerification,commitLockfile,dependencyOverrides,disableLifecycleScripts,enforceStrictSsl,filesField,frozenLockfile,frozenStore,hardenedMode,createMinimumReleaseAge(time),namedRegistries,paranoidMode,patchedDependencies,pinExactVersions,provenance,publishAccess,storeServer,strictAllowScripts,strictReleaseAge,strictStoreIntegrity,trustPolicy,unsupportedSettings];const builtinScope={"advisory-check":`installation`,"approved-git-repos":`installation`,"audit-suppression":`installation`,"block-auto-install":`installation`,"block-exotic-subdeps":`installation`,"bun-security-scanner":`installation`,"checksum-verification":`installation`,"commit-lockfile":`installation`,"dependency-overrides":`installation`,"disable-lifecycle-scripts":`installation`,"enforce-strict-ssl":`installation`,"files-field":`manifest`,"frozen-lockfile":`installation`,"frozen-store":`installation`,"hardened-mode":`installation`,"minimum-release-age":`installation`,"named-registries":`installation`,"paranoid-mode":`installation`,"patched-dependencies":`installation`,"pin-exact-versions":`installation`,provenance:`installation`,"publish-access":`manifest`,"store-server":`installation`,"strict-allow-scripts":`installation`,"strict-release-age":`installation`,"strict-store-integrity":`installation`,"trust-policy":`installation`,"unsupported-settings":`split`};const scopeOf=id=>isOwnKey(builtinScope,id)?builtinScope[id]:`custom`;const compileExclusions=patterns=>{try{const matchers=patterns.map(pattern=>{return new Minimatch(pattern.replace(/(?:\/\*\*)+$/u,``).replace(/[\\[\]]/gu,`\\$&`),{dot:true,nocase:false,nonegate:true,nocomment:true,nobrace:true,noext:true,platform:`linux`})});return directory=>matchers.some(matcher=>matcher.match(directory))}catch(error){throw new ConfigError(`exclude: ${error instanceof Error?error.message:String(error)}`)}};const applyConfig=(baseRules,config)=>{const mergedRules=[...baseRules,...config?.customRules??[]];const knownRuleIds=new Set;const duplicateRuleIds=new Set;for(const rule of mergedRules){if(knownRuleIds.has(rule.id))duplicateRuleIds.add(rule.id);knownRuleIds.add(rule.id)}if(duplicateRuleIds.size>0)throw new ConfigError(`Duplicate rule ids: ${Array.from(duplicateRuleIds,id=>`'${id}'`).join(`, `)}`);const unknownRuleLabels=[];for(const id of Object.keys(config?.rules??{}))if(!knownRuleIds.has(id))unknownRuleLabels.push(`'${id}'`);if(unknownRuleLabels.length>0)throw new ConfigError(`Unknown rule ids: ${unknownRuleLabels.join(`, `)}`);const activeRules=[];const severityOverrides=new Map;const overrides=config?.rules;for(const rule of mergedRules){const override=overrides&&Object.hasOwn(overrides,rule.id)?overrides[rule.id]:void 0;if(override===`off`)continue;activeRules.push(rule);if(override!==void 0)severityOverrides.set(rule.id,override)}return{rules:activeRules,severityOverrides}};const registerSignals=(signals,pm,ownersByFile)=>{for(const file of signals){const prior=ownersByFile.get(file);if(typeof prior!==`undefined`)throw new TypeError(`detection signal '${file}' is claimed by both '${prior}' and '${pm}'`);ownersByFile.set(file,pm)}};const buildDetectionSignals=()=>{const signalsByPM=new Map;const ownersByFile=new Map;for(const pm of PMS){const{lockfiles,configs}=PM_SIGNALS[pm];const signals=[...lockfiles,...configs];registerSignals(signals,pm,ownersByFile);signalsByPM.set(pm,signals)}return signalsByPM};const DETECTION_SIGNALS=buildDetectionSignals();const detectPMs=ctx=>{const found=new Set;const declared=ctx.packageJson?.packageManager;const declaredPM=declared===void 0?void 0:parsePackageManagerField(declared);if(declaredPM!==void 0)found.add(declaredPM);for(const pm of PMS){const signals=DETECTION_SIGNALS.get(pm);if(signals&&signals.some(file=>ctx.exists(asRelPath(file))))found.add(pm)}return PMS.filter(pm=>found.has(pm))};const resolvePMs=(ctx,options)=>{const{pmOverride,allowed}=options;const detected=pmOverride?[pmOverride]:detectPMs(ctx);const pms=allowed?detected.filter(pm=>allowed.includes(pm)):detected;if(pms.length>0)return pms;if(options.optional&&detected.length===0)return[];if(!allowed)throw new UsageError(`No package manager detected. Pass --pm <${PMS.join(`|`)}> to be explicit.`);if(pmOverride)throw new UsageError(`--pm ${pmOverride} is not in siro.config.ts pms (${allowed.join(`, `)}). Adjust the config or the flag.`);if(detected.length>0)throw new UsageError(`Detected PMs (${detected.join(`, `)}) do not match siro.config.ts pms (${allowed.join(`, `)}). Adjust the config or remove the restriction.`);throw new UsageError(`No package manager detected, and siro.config.ts restricts pms to ${allowed.join(`, `)}. Adjust the config or remove the restriction.`)};const decideSeverity=(status,binding,rule,userOverride)=>userOverride??status.severity??binding.severity??rule.severity;const buildVersionNoteParts=versionNote=>{const parts=[];if(typeof versionNote.configAvailableSince!==`undefined`)parts.push(`available since ${versionNote.configAvailableSince}`);if(typeof versionNote.defaultSafeSince!==`undefined`)parts.push(`default safe since ${versionNote.defaultSafeSince}`);if(typeof versionNote.note!==`undefined`)parts.push(versionNote.note);return parts};const renderVersionNoteMessage=(message,versionNote)=>{if(typeof versionNote===`undefined`)return message;const parts=buildVersionNoteParts(versionNote);if(parts.length===0)return message;return`${message} (${parts.join(`; `)})`};const evaluateBinding=(repository,rule,binding,overrides,target)=>{const response=binding.check({...repository.ctx,readConfig:repository.parseConfig,pmVersion:target?.version},repository.parseConfig(binding.file));assertSynchronous(response,`Rule '${rule.id}' check`);if(!isCheckStatusShape(response))throw new ConfigError(`Rule '${rule.id}' returned an invalid check result.`);if(response.state===`ok`||response.state===`na`)return[];return(response.state===`violations`?response.violations:[response]).map(status=>({ruleId:rule.id,directory:`.`,...target?{pm:target.pm}:{},severity:decideSeverity(status,binding,rule,overrides?.get(rule.id)),message:renderVersionNoteMessage(status.message,binding.versionNote),file:status.file??binding.file?.path,docs:binding.docs??rule.docs,actual:status.actual,expected:status.expected,remediation:target?guardRemediationAvailability(target.pm,target.version,status.remediation):status.remediation}))};const resolveBindingProjectType=(ctx,pm,parseConfig)=>{if(ctx.projectType!==void 0)return ctx.projectType;return pm===`deno`?resolveDenoProjectType(ctx,parseConfig(CONFIG_FILES.denoJson)):resolvePackageJsonProjectType(ctx)};const runLint=options=>{const{repository,targets,ruleSet,severityOverrides}=options;const findings=[];for(const rule of ruleSet)for(const target of targets){const{pm}=target;const binding=rule.bindings[pm];if(!binding)continue;if(rule.projectTypes){const projectType=resolveBindingProjectType(repository.ctx,pm,repository.parseConfig);if(!rule.projectTypes.includes(projectType))continue}const evaluated=evaluateBinding(repository,rule,binding,severityOverrides,target);for(const finding of evaluated)findings.push(finding)}return{findings}};const validateDenoMetadata=config=>{const name=getByPath(config,[`name`]);if(name!=null&&typeof name!==`string`)throw new ConfigError(`deno.json: name must be a string or null.`);const publish=getByPath(config,[`publish`]);if(publish==null||typeof publish===`boolean`)return;if(!isPlainRecord(publish))throw new ConfigError(`deno.json: publish must be a boolean, object or null.`);const include=getByPath(publish,[`include`]);if(include!=null&&!isStringArray(include))throw new ConfigError(`deno.json: publish.include must be a string array or null.`)};const EMPTY_FILE=Object.freeze({});const createConfigParser=(codecFor,ctx,maxDepth=DEFAULT_SCAN_LIMITS.maxConfigDepth)=>{const cache=new Map;return file=>{if(file===void 0)return EMPTY_FILE;const cacheKey=`${file.kind}:${file.path}`;const cached=cache.get(cacheKey);if(cached!==void 0)return cached;const text=ctx.readText(file.path);if(text===void 0){cache.set(cacheKey,EMPTY_FILE);return EMPTY_FILE}const parsed=wrapCodecError(file.path,()=>codecFor(file.kind).parse(text));checkConfigDepth(parsed,maxDepth);cache.set(cacheKey,parsed);return parsed}};const createRepositoryEvaluation=(ctx,codecFor,maxDepth=DEFAULT_SCAN_LIMITS.maxConfigDepth)=>({ctx,parseConfig:createConfigParser(codecFor,ctx,maxDepth)});const inspectManifests=(repository,maxConfigDepth)=>{const manifests=[];const{ctx,parseConfig}=repository;checkConfigDepth(ctx.packageJson,maxConfigDepth);if(ctx.packageJson!==void 0)manifests.push(`package.json`);if(ctx.readText(CONFIG_FILES.denoJson.path)!==void 0){validateDenoMetadata(parseConfig(CONFIG_FILES.denoJson));manifests.push(`deno.json`)}else if(ctx.exists(asRelPath(`deno.jsonc`)))throw new ConfigError(`deno.jsonc is not supported; use strict deno.json.`);return manifests};const inspectDirectory=(directory,absolute,fs,projectType,dependencies,maxConfigDepth)=>{try{const repository=createRepositoryEvaluation(dependencies.createRepoContext(absolute,fs,projectType),dependencies.codecFor,maxConfigDepth);return{directory,repository,manifests:inspectManifests(repository,maxConfigDepth)}}catch(error){if(error instanceof ConfigError&&directory!==`.`)throw new ConfigError(`${directory}/${error.message}`);throw error}};const discover=(cwd,fs,excluded,projectType,dependencies,limits=DEFAULT_SCAN_LIMITS)=>{const{paths}=dependencies;const pending=[asRelPath(`.`)];const directories=[];for(;;){const directory=pending.pop();if(directory===void 0)break;checkLimit(`maxDirectories`,directories.length+pending.length+1,limits);checkLimit(`maxDirectoryDepth`,directory===`.`?0:directory.split(`/`).length,limits);const absolute=paths.resolve(cwd,directory);directories.push(inspectDirectory(directory,absolute,fs,projectType,dependencies,limits.maxConfigDepth));const names=fs.readDirectories(absolute);const invalidEnumeration=`${directory}: FileSystem.readDirectories must return a dense array.`;if(!Array.isArray(names))throw new ConfigError(invalidEnumeration);const children=new Set;for(let index=0;index<names.length;index++){if(!Object.hasOwn(names,index))throw new ConfigError(invalidEnumeration);const name=names[index];const child=paths.child(directory,name);if(name===`.git`||name===`node_modules`||excluded(child)||children.has(child))continue;checkLimit(`maxDirectoryDepth`,child.split(`/`).length,limits);checkLimit(`maxDirectories`,directories.length+pending.length+1,limits);children.add(child);pending.push(child)}}return directories.sort((left,right)=>{if(left.directory<right.directory)return-1;if(left.directory>right.directory)return 1;return 0})};const unsupportedManifestBindings={"package.json":createUnsupportedSettings(file=>file===`package.json`).bindings,"deno.json":createUnsupportedSettings(file=>file===`deno.json`).bindings};const manifestProjectType=(repository,file)=>file===`deno.json`?resolveDenoProjectType(repository.ctx,repository.parseConfig(CONFIG_FILES.denoJson)):resolvePackageJsonProjectType(repository.ctx);const selectManifestBinding={"files-field":file=>file===`deno.json`?denoPublishBinding:packageJsonFilesBinding,"publish-access":(file,repository,targets)=>{if(file!==`package.json`)return void 0;return repository.ctx.packageJson?.publishConfig?.access===`private`&&targets.length>0?`use-pm-bindings`:publishAccessBinding}};const checkManifest=(repository,file,targets,rules,overrides)=>{const projectType=manifestProjectType(repository,file);const findings=[];for(const rule of rules){if(rule.projectTypes&&!rule.projectTypes.includes(projectType))continue;if(rule.id===`unsupported-settings`){const result=runLint({repository,targets,ruleSet:[{...rule,bindings:unsupportedManifestBindings[file]}],severityOverrides:overrides});for(const finding of result.findings)findings.push(finding);continue}if(!isOwnKey(selectManifestBinding,rule.id))continue;const binding=selectManifestBinding[rule.id](file,repository,targets);if(binding===void 0)continue;const evaluated=binding===`use-pm-bindings`?runLint({repository,targets,ruleSet:[rule],severityOverrides:overrides}).findings:evaluateBinding(repository,rule,binding,overrides);for(const finding of evaluated)findings.push(finding)}return findings};const rebaseRemediation=(directory,remediation)=>{if(!remediation)return void 0;if(remediation.kind===`manual`)return{...remediation,steps:[`Work in ${directory} for this finding.`,...remediation.steps]};const rebaseOperation=operation=>({...operation,file:{...operation.file,path:joinRelativePath(directory,operation.file.path)}});const[first,...rest]=remediation.operations;return{...remediation,operations:[rebaseOperation(first),...rest.map(rebaseOperation)]}};const rebaseFinding=(directory,finding)=>{if(directory===`.`)return{...finding,directory};return{...finding,directory,...finding.file===void 0?{}:{file:`${directory}/${finding.file}`},remediation:rebaseRemediation(directory,finding.remediation)}};const boundedFileSystem=(fs,limits)=>{const textCache=new Map;let totalTextBytes=0;let returnedEntryCount=0;return{exists:path=>fs.exists(path),readText(path){if(!textCache.has(path)){const text=fs.readText(path);if(text!==void 0){const textBytes=utf8Bytes(text);checkLimit(`maxFileBytes`,textBytes,limits);totalTextBytes+=textBytes;checkLimit(`maxTotalBytes`,totalTextBytes,limits)}textCache.set(path,text)}return textCache.get(path)},readDirectories(path){const names=fs.readDirectories(path);if(Array.isArray(names)){returnedEntryCount+=names.length;checkLimit(`maxEntries`,returnedEntryCount,limits)}return names}}};const validateLintOptions=(options,paths)=>{if(!options||!paths.isAbsolute(options.cwd))throw new UsageError(`cwd must be an absolute filesystem path.`);if(`workspaces`in options)throw new UsageError(`workspaces was removed in 0.6.0. Package discovery is recursive by default; use exclude and installationRoots to specify scope.`);if(`customRules`in options||`reporters`in options)throw new UsageError(`Pass customRules and reporters inside the config option.`);if(options.pm!==void 0&&!isPM(options.pm))throw new UsageError(`Unknown package manager: ${String(options.pm)}`);if(options.projectType!==void 0&&!isProjectType(options.projectType))throw new UsageError(`Unknown project type: ${String(options.projectType)}`);if(options.pmVersion!==void 0&&(!options.pm||!isStableVersion(options.pmVersion)))throw new UsageError(`pmVersion / --pm-version requires pm / --pm and an exact stable version such as 10.16.0.`)};const attachTargetVersions=(pms,versions)=>pms.map(pm=>({pm,...versions[pm]===void 0?{}:{version:versions[pm]}}));const resolveDirectoryTargets=(item,installationRoot,options,config,hasActiveCustomRules)=>{const{ctx}=item.repository;const declaredVersions=declaredPMVersion(ctx.packageJson?.packageManager);if(item.directory===`.`)return attachTargetVersions(resolvePMs(ctx,{allowed:config?.pms,pmOverride:options.pm,optional:!installationRoot&&!hasActiveCustomRules}),{...declaredVersions,...config?.pmVersions,...options.pm&&options.pmVersion?{[options.pm]:options.pmVersion}:{}});if(installationRoot)try{return attachTargetVersions(resolvePMs(ctx,{pmOverride:installationRoot.pm}),{...declaredVersions,...installationRoot.pm&&installationRoot.pmVersion?{[installationRoot.pm]:installationRoot.pmVersion}:{}})}catch(error){if(error instanceof UsageError)throw new UsageError(`${item.directory}: ${error.message}`);throw error}const declared=ctx.packageJson?.packageManager===void 0?void 0:parsePackageManagerField(ctx.packageJson.packageManager);return attachTargetVersions(PMS.filter(pm=>pm===declared||pm===`deno`&&item.manifests.includes(`deno.json`)),declaredVersions)};const prepareLint=(options,dependencies)=>{validateLintOptions(options,dependencies.paths);const config=options.config===void 0?void 0:parseConfig(options.config);const limits=resolveScanLimits(options.limits);const sourceFs=options.fs===void 0?dependencies.fileSystem:options.fs;if(typeof sourceFs.readDirectories!==`function`)throw new ConfigError(`FileSystem.readDirectories is required for package discovery; no native filesystem fallback is used.`);const fs=boundedFileSystem(sourceFs,limits);const excluded=dependencies.compileExclusions(parseExcludes(options.exclude!==void 0?options.exclude:config?.exclude??[]));const installations=new Map(parseInstallationRoots(options.installationRoots!==void 0?options.installationRoots:config?.installationRoots??[`.`]).map(root=>[root.path,root]));const configured=applyConfig(dependencies.rules,config);const hasActiveCustomRules=configured.rules.some(rule=>scopeOf(rule.id)===`custom`);const discovered=discover(options.cwd,fs,excluded,options.projectType??config?.projectType,dependencies,limits);const selectedDirectories=new Set(discovered.map(item=>item.directory));for(const path of installations.keys())if(!selectedDirectories.has(path))throw new UsageError(`${path}: installation root must be an existing, selected ordinary directory using its exact enumerated spelling (no symlinks, excluded or hard-skipped paths).`);return{evaluation:{directories:discovered.map(item=>{const installationRoot=installations.get(item.directory);return{...item,isInstallationRoot:installationRoot!==void 0,targets:resolveDirectoryTargets(item,installationRoot,options,config,hasActiveCustomRules)}}),ruleSet:configured.rules,severityOverrides:configured.severityOverrides,limits},reporters:config?.reporters??[]}};const unsupportedInstallationBindings=createUnsupportedSettings(file=>file!==`package.json`&&file!==`deno.json`).bindings;const groupRulesByScope=(ruleSet,includeRootInstallation)=>{const manifestRules=[];const installationRules=[];const rootRules=[];for(const rule of ruleSet){const scope=scopeOf(rule.id);if(scope===`manifest`||scope===`split`)manifestRules.push(rule);if(scope===`custom`){rootRules.push(rule);continue}if(scope!==`installation`&&scope!==`split`)continue;const installationRule=scope===`split`?{...rule,bindings:unsupportedInstallationBindings}:rule;installationRules.push(installationRule);if(includeRootInstallation)rootRules.push(installationRule)}return{manifestRules,installationRules,rootRules}};const runPreparedLint=evaluation=>{const{directories,ruleSet,severityOverrides,limits}=evaluation;const findings=[];const manifests=[];const installationRoots=[];const ruleOrder=new Map(ruleSet.map((rule,index)=>[rule.id,index]));const{manifestRules,installationRules,rootRules}=groupRulesByScope(ruleSet,directories.some(item=>item.directory===`.`&&item.isInstallationRoot));for(const item of directories){const{directory,repository,targets,isInstallationRoot}=item;const directoryFindings=[];for(const file of item.manifests){const manifestTargets=targets.filter(({pm})=>file===`deno.json`?pm===`deno`:pm!==`deno`);manifests.push({path:directory===`.`?file:`${directory}/${file}`,projectType:manifestProjectType(repository,file),targets:manifestTargets});const manifestFindings=checkManifest(repository,file,manifestTargets,manifestRules,severityOverrides);for(const finding of manifestFindings)directoryFindings.push(finding)}if(isInstallationRoot)installationRoots.push({directory,targets});if(directory===`.`||isInstallationRoot)try{const installationFindings=runLint({repository,targets,ruleSet:directory===`.`?rootRules:installationRules,severityOverrides}).findings;for(const finding of installationFindings)directoryFindings.push(finding)}catch(error){if(error instanceof ConfigError&&directory!==`.`)throw new ConfigError(`${directory}/${error.message}`);throw error}directoryFindings.sort((left,right)=>(ruleOrder.get(left.ruleId)??0)-(ruleOrder.get(right.ruleId)??0));checkLimit(`maxFindings`,findings.length+directoryFindings.length,limits);for(const finding of directoryFindings)findings.push(rebaseFinding(directory,finding))}const summary={error:0,warn:0,info:0};for(const finding of findings)summary[finding.severity]+=1;return{findings,summary,inspection:{manifests,installationRoots}}};const lint$1=(options,dependencies)=>runPreparedLint(prepareLint(options,dependencies).evaluation);const EXIT_SUCCESS=0;const EXIT_FAILURE=1;const meetsThreshold=(severity,threshold)=>SEVERITY_RANK[severity]>=SEVERITY_RANK[threshold];const filterBySeverity=(result,threshold)=>{const findings=[];const summary={error:0,info:0,warn:0};for(const finding of result.findings)if(meetsThreshold(finding.severity,threshold)){findings.push(finding);summary[finding.severity]+=1}return{...result,findings,summary}};const exitCodeForLint=(result,threshold=`error`)=>{if(result.findings.some(finding=>meetsThreshold(finding.severity,threshold)))return EXIT_FAILURE;return EXIT_SUCCESS};const lintCommand$1=async(options,io,dependencies,reporters)=>{if(!options)throw new UsageError(`Lint options are required.`);if(options.severity!==void 0&&!isSeverity(options.severity))throw new UsageError(`Invalid severity: ${String(options.severity)}`);const prepared=prepareLint(options,dependencies);const registry=reporters.createRegistry(prepared.reporters);const selection=options.reporter??reporters.defaultName;const reporter=typeof selection===`string`?registry.get(selection):selection;if(!isReporterShape(reporter))throw new UsageError(`${typeof selection===`string`?`Unknown`:`Invalid`} reporter: ${String(selection)} (available: ${[...registry.keys()].join(`, `)})`);const result=runPreparedLint(prepared.evaluation);const exitCode=exitCodeForLint(result,options.severity??`error`);await reportAndAwaitWrites(reporter,filterBySeverity(result,options.severity??`info`),io,{cwd:options.cwd,limits:prepared.evaluation.limits});return exitCode};const reportAndAwaitWrites=async(reporter,result,io,context)=>{const pendingWrites=[];const consumeOutput=outputBudget(context.limits.maxOutputBytes);let outputFailure;const recordFailure=error=>{outputFailure??={error}};const trackWrite=write=>line=>{try{consumeOutput(`${line}\n`);const written=write(line);pendingWrites.push(Promise.resolve(written).then(()=>{},recordFailure));return written}catch(error){recordFailure(error);throw error}};try{await reporter.format(result,{stdout:trackWrite(line=>io.stdout(line)),stderr:trackWrite(line=>io.stderr(line))},context)}finally{await Promise.all(pendingWrites)}if(outputFailure)throw outputFailure.error};const PackageJsonSchema=vb.looseObject({files:vb.optional(vb.array(vb.string())),name:vb.optional(vb.string()),packageManager:vb.optional(vb.string()),private:vb.optional(vb.boolean()),publishConfig:vb.optional(vb.looseObject({access:vb.optional(vb.picklist([`public`,`restricted`,`private`])),provenance:vb.optional(vb.boolean())})),trustedDependencies:vb.optional(vb.array(vb.string()))});const parsePackageJson=value=>{if(!isPlainRecord(value))throw new ConfigError(`package.json: expected an object at the root.`);const result=vb.safeParse(PackageJsonSchema,value);if(result.success)return result.output;const issue=result.issues[0];const field=vb.getDotPath(issue);throw new ConfigError(`package.json: ${field?`${field}: `:``}${issue.message}`)};const decodeJson=text=>{try{return JSON.parse(text.trim())}catch{throw new ConfigParseError(`invalid JSON`)}};const parseJson=(text,maxDepth=DEFAULT_SCAN_LIMITS.maxConfigDepth)=>{const value=decodeJson(text);checkConfigDepth(value,maxDepth);return value};const jsonCodec={parse(text){return toParsedConfig(parseJson(text))}};const createRepoContext=(root,fs,projectType,maxDepth=DEFAULT_SCAN_LIMITS.maxConfigDepth)=>{const readText=relative=>fs.readText(nodePaths.resolve(root,relative));const raw=readText(asRelPath(`package.json`));const packageJson=raw===void 0?void 0:parsePackageJson(wrapCodecError(`package.json`,()=>parseJson(raw,maxDepth)));const exists=relative=>fs.exists(nodePaths.resolve(root,relative));return{exists,packageJson,projectType,readText,root}};const INTEGER_PATTERN=/^-?\d+$/u;const parseBooleanString=text=>{if(text===`true`)return true;if(text===`false`)return false};const parseIntegerString=text=>{if(INTEGER_PATTERN.test(text))return Number(text)};const coerceIniScalar=raw=>{if(typeof raw!==`string`)return raw;return parseBooleanString(raw)??parseIntegerString(raw)??raw};const iniCodec={parse(text){const raw=toParsedConfig(ini.parse(text));const config={};for(const[key,value]of Object.entries(raw))if(Array.isArray(value))config[key]=value.map(coerceIniScalar);else if(typeof value===`object`&&value!==null)config[key]=toParsedConfig(value);else config[key]=coerceIniScalar(value);return config}};const decodeToml=text=>{const input=text.startsWith(``)?text.slice(1):text;try{return parse$1(input)}catch{throw new ConfigParseError(`Invalid TOML syntax.`)}};const tomlCodec={parse(text){if(text.trim()===``)return{};return toParsedConfig(decodeToml(text))}};const decodeYaml=text=>{try{const document=parseDocument(text,{prettyErrors:false});if(document.errors.length)throw new ConfigParseError(`Invalid YAML syntax.`);if(document.contents===null)return{};return document.toJS()}catch(error){if(error instanceof ConfigParseError)throw error;if(error instanceof Error&&error.message.includes(`Excessive alias count`))throw new ConfigParseError(`Excessive alias count in YAML.`);throw new ConfigParseError(`Invalid YAML.`)}};const parseYaml=(text,maxDepth=DEFAULT_SCAN_LIMITS.maxConfigDepth)=>{if(text.trim()===``)return{};const value=decodeYaml(text);try{checkConfigDepth(value,maxDepth)}catch(error){if(error instanceof ConfigError)throw error;throw new ConfigParseError(`Invalid YAML structure.`)}return toParsedConfig(value)};const yamlCodec={parse:text=>parseYaml(text)};const CODECS={json:jsonCodec,npmrc:iniCodec,toml:tomlCodec,yaml:yamlCodec};const createBoundedCodec=(kind,limits)=>{if(kind===`json`)return{parse:text=>toParsedConfig(parseJson(text,limits.maxConfigDepth))};if(kind===`yaml`)return{parse:text=>parseYaml(text,limits.maxConfigDepth)};return{parse(text){const config=CODECS[kind].parse(text);checkConfigDepth(config,limits.maxConfigDepth);return config}}};const createCodecFor=limits=>{const codecs={};return kind=>codecs[kind]??=createBoundedCodec(kind,limits)};const COMMAND={error:`error`,info:`notice`,warn:`warning`};const escapeData=raw=>raw.replaceAll(`%`,`%25`).replaceAll(`\r`,`%0D`).replaceAll(`
2
+ `,`%0A`);const escapeProperty=raw=>escapeData(raw).replaceAll(`:`,`%3A`).replaceAll(`,`,`%2C`);const githubReporter={async format(result,io,context){const consume=outputBudget(context.limits?.maxOutputBytes??DEFAULT_SCAN_LIMITS.maxOutputBytes);for(const finding of result.findings){const fileProperty=finding.file?`file=${escapeProperty(path.resolve(context.cwd,finding.file))},`:``;const docsSuffix=finding.docs?` (${finding.docs})`:``;const body=`[${finding.pm??`package`}] ${finding.directory}: ${finding.message}${docsSuffix}`;const line=`::${COMMAND[finding.severity]} ${fileProperty}title=${escapeProperty(finding.ruleId)}::${escapeData(body)}`;consume(`${line}\n`);await io.stdout(line)}},name:`github`};const escapeControlCharacter=character=>`\\u${character.charCodeAt(0).toString(16).padStart(4,`0`)}`;const safeText=text=>text.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/gu,escapeControlCharacter).replaceAll(`##[`,`#\\u0023[`).replace(/^(\s*)::/u,`$1\\u003a:`);const safeJsonText=json=>json.replace(/[\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/gu,escapeControlCharacter).replaceAll(`##[`,`\\u0023\\u0023[`);const isOmittedJsonValue=value=>value===void 0||typeof value===`function`||typeof value===`symbol`;const normalizeJsonValue=(value,key)=>{const toJSON=value!==null&&(typeof value===`object`||typeof value===`function`||typeof value===`bigint`)?Reflect.get(Object(value),`toJSON`,value):void 0;const normalized=typeof toJSON===`function`?Reflect.apply(toJSON,value,[key]):value;if(types.isNumberObject(normalized))return+normalized;if(types.isStringObject(normalized))return`${normalized}`;if(types.isBooleanObject(normalized))return Boolean.prototype.valueOf.call(normalized);if(types.isBigIntObject(normalized))return BigInt.prototype.valueOf.call(normalized);return normalized};const boundedJson=(input,maxBytes,maxDepth)=>{const consume=outputBudget(maxBytes);const chunks=[];const activeContainers=new Set;const emit=text=>{const escaped=safeJsonText(text);consume(escaped);chunks.push(escaped)};const serialize=(value,depth)=>{if(typeof value===`bigint`)throw new TypeError(`Do not know how to serialize a BigInt`);if(value===null||typeof value!==`object`){emit(JSON.stringify(value)??`null`);return}if(activeContainers.has(value))throw new TypeError(`Cannot serialize a circular report.`);if(depth>maxDepth)throw new TypeError(`Report nesting exceeds the configured depth.`);activeContainers.add(value);const isArray=Array.isArray(value);emit(isArray?`[`:`{`);let hasEntries=false;const serializeEntry=(key,raw)=>{const child=normalizeJsonValue(raw,key);if(!isArray&&isOmittedJsonValue(child))return;emit(`${hasEntries?`,
3
+ `:`
4
+ `}${` `.repeat(depth)}`);hasEntries=true;if(!isArray){emit(JSON.stringify(key));emit(`: `)}serialize(isOmittedJsonValue(child)?null:child,depth+1)};if(isArray){const length=value.length;for(let index=0;index<length;index++)serializeEntry(String(index),value[index])}else for(const key of Object.keys(value))serializeEntry(key,Reflect.get(value,key));if(hasEntries)emit(`\n${` `.repeat(depth-1)}`);emit(isArray?`]`:`}`);activeContainers.delete(value)};serialize(normalizeJsonValue(input,``),1);consume(`
5
+ `);return chunks.join(``)};const SCHEMA_VERSION=3;const REPORT_ENVELOPE_DEPTH=16;const jsonReporter={async format(result,io,context){await io.stdout(boundedJson({schemaVersion:SCHEMA_VERSION,siroVersion:version,findings:result.findings,summary:result.summary,inspection:result.inspection},context.limits?.maxOutputBytes??DEFAULT_SCAN_LIMITS.maxOutputBytes,(context.limits?.maxConfigDepth??DEFAULT_SCAN_LIMITS.maxConfigDepth)+REPORT_ENVELOPE_DEPTH))},name:`json`};const GLYPH={error:`✖ error`,info:`ℹ info`,warn:`⚠ warn`};const colorSupportedNow=()=>{const{env}=process;if(typeof env.NO_COLOR!==`undefined`&&env.NO_COLOR!==``)return false;if(`FORCE_COLOR`in env)return env.FORCE_COLOR!==`0`&&env.FORCE_COLOR!==``;return pc.isColorSupported};const renderFinding=(finding,palette,appendLine)=>{const location=palette.colors.dim(` (${safeText(finding.file??finding.directory)})`);appendLine(`${palette.tag[finding.severity](GLYPH[finding.severity])} [${finding.pm??`package`}] ${palette.colors.bold(safeText(finding.ruleId))}${location}`);appendLine(` ${safeText(finding.message)}`);if(finding.remediation?.kind===`manual`)for(const step of finding.remediation.steps)appendLine(` ↳ ${safeText(step)}`);if(finding.docs)appendLine(palette.colors.dim(` → ${safeText(finding.docs)}`))};const createRenderPalette=()=>{const colors=pc.createColors(colorSupportedNow());return{colors,tag:{error:colors.red,info:colors.cyan,warn:colors.yellow}}};const prettyReporter={async format(result,io,context){const lines=[];const consume=outputBudget(context.limits?.maxOutputBytes??DEFAULT_SCAN_LIMITS.maxOutputBytes);const collect=line=>{consume(`${line}\n`);lines.push(line)};const palette=createRenderPalette();const installationDirectories=result.inspection.installationRoots.map(root=>safeText(root.directory)).join(`, `)||`none`;collect(`Inspection: ${result.inspection.manifests.length} manifests; installation roots: ${installationDirectories}.`);collect(`Unknown PM/version targets have no availability assessment; installation scope is explicit.`);if(result.findings.length===0)collect(palette.colors.green(`✔ No security best-practice issues found.`));else{for(const finding of result.findings)renderFinding(finding,palette,collect);const{error,warn,info}=result.summary;collect(``);collect(`Summary: ${error} error, ${warn} warn, ${info} info`)}await io.stdout(lines.join(`
6
+ `))},name:`pretty`};const BUILTINS=[prettyReporter,jsonReporter,githubReporter];const DEFAULT_REPORTER_NAME=prettyReporter.name;const JSON_REPORTER_NAME=jsonReporter.name;const BUILTIN_REPORTER_NAMES=BUILTINS.map(reporter=>reporter.name);const createRegistry=(extras=[])=>{const registry=new Map;for(const reporter of BUILTINS)registry.set(reporter.name,reporter);for(const reporter of extras)registry.set(reporter.name,reporter);return registry};const rules=createBuiltinRules({now:()=>Date.now(),parse:value=>Date.parse(value)});const prepareRuntime=options=>{if(!options||!nodePaths.isAbsolute(options.cwd))throw new UsageError(`cwd must be an absolute filesystem path.`);const limits=resolveScanLimits(options.limits);if(options.rejectSymlinks!==void 0&&typeof options.rejectSymlinks!==`boolean`)throw new UsageError(`rejectSymlinks must be a boolean.`);const usesNativeFileSystem=options.fs===void 0||options.fs===nodeFileSystem;if(options.rejectSymlinks&&!usesNativeFileSystem)throw new UsageError(`rejectSymlinks requires the native filesystem; an injected FileSystem is trusted code.`);if(usesNativeFileSystem)assertDirectory(options.cwd);const dependencies={rules,fileSystem:usesNativeFileSystem?createNodeFileSystem(limits,options.rejectSymlinks?options.cwd:void 0):options.fs,paths:nodePaths,createRepoContext:(root,fs,projectType)=>createRepoContext(root,fs,projectType,limits.maxConfigDepth),codecFor:createCodecFor(limits),compileExclusions};return{options:options.fs===nodeFileSystem?{...options,fs:void 0}:options,dependencies}};const lint=options=>{const prepared=prepareRuntime(options);return lint$1(prepared.options,prepared.dependencies)};const lintCommand=async(options,io)=>{const prepared=prepareRuntime(options);return lintCommand$1(prepared.options,io,prepared.dependencies,{defaultName:DEFAULT_REPORTER_NAME,createRegistry})};const createStreamLineWriter=stream=>{let failure;const pendingRejects=new Set;const fail=cause=>{failure??=new Error(`Output failed: ${cause instanceof Error?cause.message:String(cause)}`,{cause});for(const reject of pendingRejects)reject(failure);pendingRejects.clear();return failure};let isObservingErrors=false;return line=>{if(!isObservingErrors){isObservingErrors=true;stream.on(`error`,fail);stream.on(`close`,()=>fail(new Error(`Output stream closed.`)))}return new Promise((resolve,reject)=>{if(failure||stream.destroyed){reject(failure??fail(new Error(`Output stream is destroyed.`)));return}pendingRejects.add(reject);try{stream.write(`${line}\n`,error=>{pendingRejects.delete(reject);if(error)reject(fail(error));else resolve()})}catch(error){fail(error)}})}};const createNodeIO=(stdout,stderr)=>({stdout:createStreamLineWriter(stdout),stderr:createStreamLineWriter(stderr)});const nodeIO=createNodeIO(process.stdout,process.stderr);export{nodeFileSystem as A,isSeverity as C,asAbsPath as D,version as E,ConfigError as F,SiroError as I,UsageError as L,DEFAULT_SCAN_LIMITS as M,resolveScanLimits as N,asRelPath as O,isOwnKey as P,isPM as S,SUPPORTED_NODE_RANGE as T,PROJECT_TYPES as _,DEFAULT_REPORTER_NAME as a,PMS as b,jsonReporter as c,overrideBindings as d,requireConfigKey as f,getByPath as g,defineRule as h,BUILTIN_REPORTER_NAMES as i,isNodeError as j,assertDirectory as k,safeText as l,loadConfig as m,lint as n,JSON_REPORTER_NAME as o,CONFIG_FILES as p,lintCommand as r,prettyReporter as s,nodeIO as t,githubReporter as u,isProjectType as v,isSupportedNodeVersion as w,SEVERITIES as x,isStableVersion as y};
7
+ //# sourceMappingURL=node-io-DE7Nzlal.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pho9ubenaa/siro",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Security best-practices linter for the npm ecosystem (npm, pnpm, deno, bun, yarn, aube).",
5
5
  "keywords": [
6
6
  "aube",