sync: update from private repo (2336949a)
CI / build-and-test (push) Canceled after 0s

This commit is contained in:
oss-sync
2026-09-17 11:29:31 +00:00
parent 28f74c4e37
commit 57702b4f34
2139 changed files with 321270 additions and 17494 deletions
+318
View File
@@ -0,0 +1,318 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { checkLockfilePolicy, isAllowedRegistryUrl, main } from './dependency-policy.mjs';
const registryOrigin = 'https://registry.npmjs.org/';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const readJson = (relative) =>
JSON.parse(fs.readFileSync(path.join(root, relative), 'utf8'));
describe('checkLockfilePolicy', () => {
it('rejects remote tarballs, missing integrity, and unapproved install scripts', () => {
const violations = checkLockfilePolicy(
{
lockfileVersion: 3,
packages: {
'node_modules/evil': {
resolved: 'https://evil.example/evil-1.0.0.tgz',
hasInstallScript: true,
},
},
},
[],
registryOrigin,
);
expect(violations).toEqual([
{ code: 'non-registry-url', name: 'node_modules/evil' },
{ code: 'unapproved-install-script', name: 'node_modules/evil' },
{ code: 'missing-integrity', name: 'node_modules/evil' },
]);
});
it('accepts an exact lockfile path that is explicitly allowlisted', () => {
const violations = checkLockfilePolicy(
{
lockfileVersion: 3,
packages: {
'node_modules/native-package': {
resolved: 'https://registry.npmjs.org/native-package/-/native-package-1.0.0.tgz',
integrity: 'sha512-example',
hasInstallScript: true,
},
},
},
['node_modules/native-package'],
registryOrigin,
);
expect(violations).toEqual([]);
});
it('does not let a package-name allowlist approve a nested copy implicitly', () => {
const violations = checkLockfilePolicy(
{
lockfileVersion: 3,
packages: {
'node_modules/parent/node_modules/native-package': {
resolved: 'https://registry.npmjs.org/native-package/-/native-package-2.0.0.tgz',
integrity: 'sha512-example',
hasInstallScript: true,
},
},
},
['node_modules/native-package'],
registryOrigin,
);
expect(violations).toContainEqual({
code: 'unapproved-install-script',
name: 'node_modules/parent/node_modules/native-package',
});
});
it('rejects unsupported lockfile formats instead of silently passing', () => {
expect(() =>
checkLockfilePolicy({ lockfileVersion: 2, packages: {} }, [], registryOrigin),
).toThrow(/lockfile version 3/i);
});
});
describe('this repository', () => {
// 許可一覧を実物の lockfile と突き合わせる。install script を持つ依存が増えた/
// 場所が変わった/一覧から消えた場合、CI の前に npm test で落ちる。
it.each(['package-lock.json', 'ui/package-lock.json'])(
'satisfies the install script policy for %s',
(lockfilePath) => {
const policy = readJson('security/install-scripts-allowlist.json');
// link の判定は lockfile の置き場所を基準に解決する。CI の入口
// (dependency-policy.mjs の main)と同じ値を渡さないと、ui/ の
// ../packages/... が「リポジトリ外」と判定されてしまう。
const lockfileDir = path.posix.dirname(lockfilePath);
const violations = checkLockfilePolicy(
readJson(lockfilePath),
policy.lockfiles[lockfilePath],
registryOrigin,
lockfileDir,
);
expect(violations).toEqual([]);
},
);
});
describe('in-repo workspace entries', () => {
const workspaceLockfile = {
lockfileVersion: 3,
packages: {
'node_modules/@scope/contract': { resolved: 'packages/contract', link: true },
'packages/contract': { name: '@scope/contract', version: '0.0.0' },
},
};
it('accepts a link whose target is a workspace source in the same lockfile', () => {
expect(checkLockfilePolicy(workspaceLockfile, [], registryOrigin)).toEqual([]);
});
it('accepts a parent-relative workspace path as npm writes it from a sub-package', () => {
// ui/package-lock.json から見た ../packages/... の形。lockfile が ui/ に在るので
// 1 つ上へ戻ってもリポジトリの内側に収まる。
const violations = checkLockfilePolicy(
{
lockfileVersion: 3,
packages: {
'node_modules/@scope/contract': { resolved: '../packages/contract', link: true },
'../packages/contract': { name: '@scope/contract', version: '0.0.0' },
},
},
[],
registryOrigin,
'ui',
);
expect(violations).toEqual([]);
});
// ここから 3 件は、実体キーを併記すれば外部を指す link が素通りした穴を塞ぐもの
// (PR #1074 のレビューが実証)。lockfile を書ける側が link と実体キーの両方を
// 握れるので、「同じ lockfile に在るか」だけでは歯止めにならない。
it('rejects a link that escapes the repository even when its target key is present', () => {
const violations = checkLockfilePolicy(
{
lockfileVersion: 3,
packages: {
'node_modules/evil': { resolved: '../../../../tmp/evil-payload', link: true },
'../../../../tmp/evil-payload': { name: 'evil', version: '1.0.0' },
},
},
[],
registryOrigin,
'.',
);
expect(violations).toContainEqual({ code: 'unsupported-link', name: 'node_modules/evil' });
});
it('rejects an absolute link target even when its key is present', () => {
const violations = checkLockfilePolicy(
{
lockfileVersion: 3,
packages: {
'node_modules/evil': { resolved: '/etc/evil', link: true },
'/etc/evil': { name: 'evil', version: '1.0.0' },
},
},
[],
registryOrigin,
'.',
);
expect(violations).toContainEqual({ code: 'unsupported-link', name: 'node_modules/evil' });
});
it('rejects a sub-package link that climbs past the repository root', () => {
// ui/ から 2 つ上がればリポジトリの外。1 つまでなら正当(上のテスト)。
const violations = checkLockfilePolicy(
{
lockfileVersion: 3,
packages: {
'node_modules/evil': { resolved: '../../outside/evil', link: true },
'../../outside/evil': { name: 'evil', version: '1.0.0' },
},
},
[],
registryOrigin,
'ui',
);
expect(violations).toContainEqual({ code: 'unsupported-link', name: 'node_modules/evil' });
});
it('still rejects a link whose target is absent from the lockfile', () => {
const violations = checkLockfilePolicy(
{
lockfileVersion: 3,
packages: {
'node_modules/@scope/contract': { resolved: '../../outside/contract', link: true },
},
},
[],
registryOrigin,
);
expect(violations).toContainEqual({
code: 'unsupported-link',
name: 'node_modules/@scope/contract',
});
});
it('still rejects an install script on a workspace source that is not allowlisted', () => {
const violations = checkLockfilePolicy(
{
lockfileVersion: 3,
packages: {
'packages/contract': { name: '@scope/contract', version: '0.0.0', hasInstallScript: true },
},
},
[],
registryOrigin,
);
expect(violations).toContainEqual({
code: 'unapproved-install-script',
name: 'packages/contract',
});
});
});
describe('allowlist rot', () => {
it('reports an allowlisted path that no longer exists in the lockfile', () => {
const violations = checkLockfilePolicy(
{ lockfileVersion: 3, packages: {} },
['node_modules/removed'],
registryOrigin,
);
expect(violations).toContainEqual({
code: 'stale-allowlist-entry',
name: 'node_modules/removed',
});
});
it('reports an allowlisted package that no longer has an install script', () => {
const violations = checkLockfilePolicy(
{
lockfileVersion: 3,
packages: {
'node_modules/calm': {
resolved: 'https://registry.npmjs.org/calm/-/calm-1.0.0.tgz',
integrity: 'sha512-example',
},
},
},
['node_modules/calm'],
registryOrigin,
);
expect(violations).toContainEqual({
code: 'stale-allowlist-entry',
name: 'node_modules/calm',
});
});
});
// レジストリ判定は 6 つの比較でできている。PR #1074 のレビューが、そのうち
// 4 つ(protocol / port / userinfo / pathname)をそれぞれ削っても 35 件すべて
// 緑のままだと報告した。効いていたのは hostname 一致だけ。判定そのものは
// 正しく動いているので、守るものが無いという指摘。1 つずつ固定する。
describe('registry url check — each comparison earns its keep', () => {
const ok = 'https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz';
it('accepts the real registry', () => {
expect(isAllowedRegistryUrl(ok, registryOrigin)).toBe(true);
});
it.each([
['protocol — 平文に落とす', 'http://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz'],
['port — 別のポートへ向ける', 'https://registry.npmjs.org:8443/left-pad/-/left-pad-1.3.0.tgz'],
['userinfo — 利用者名だけ埋める', 'https://[email protected]/left-pad/-/left-pad-1.3.0.tgz'],
['userinfo — 合言葉だけ埋める', 'https://:[email protected]/left-pad/-/left-pad-1.3.0.tgz'],
['userinfo — 両方埋める', 'https://user:[email protected]/left-pad/-/left-pad-1.3.0.tgz'],
['userinfo — @ で別ホストへ逃がす', 'https://[email protected]/left-pad.tgz'],
['hostname — 似た名前の別ホスト', 'https://registry.npmjs.org.evil.example/left-pad.tgz'],
['hostname — 部分一致を狙う', 'https://evil-registry.npmjs.org.example/left-pad.tgz'],
])('rejects %s', (_label, url) => {
expect(isAllowedRegistryUrl(url, registryOrigin)).toBe(false);
});
it('rejects a registry origin with a path prefix when the url leaves it', () => {
// pathname 比較を消すと、別のパス配下でも通ってしまう
expect(isAllowedRegistryUrl('https://example.test/other/x.tgz', 'https://example.test/scoped/')).toBe(false);
expect(isAllowedRegistryUrl('https://example.test/scoped/x.tgz', 'https://example.test/scoped/')).toBe(true);
});
});
describe('the entry point CI actually runs', () => {
// main は CI が叩く唯一の入口なのに、どのテストからも呼ばれていなかった
// (PR #1074 のレビュー P2-2)。純関数だけ固めて配線が無テスト、の典型。
it('throws when a lockfile has no allowlist defined', async () => {
await expect(main(['no-such-lockfile-in-the-policy.json'])).rejects.toThrow();
});
it('requires at least one lockfile path', async () => {
await expect(main([])).rejects.toThrow(/usage/);
});
it('leaves the exit code alone when this repository passes', async () => {
const before = process.exitCode;
process.exitCode = 0;
await main(['package-lock.json', 'ui/package-lock.json']);
expect(process.exitCode).toBe(0);
process.exitCode = before;
});
});