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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .yarn/versions/e1c54913.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
releases:
"@yarnpkg/cli": minor
"@yarnpkg/plugin-git": minor

declined:
- "@yarnpkg/plugin-compat"
- "@yarnpkg/plugin-constraints"
- "@yarnpkg/plugin-dlx"
- "@yarnpkg/plugin-essentials"
- "@yarnpkg/plugin-github"
- "@yarnpkg/plugin-init"
- "@yarnpkg/plugin-interactive-tools"
- "@yarnpkg/plugin-nm"
- "@yarnpkg/plugin-npm-cli"
- "@yarnpkg/plugin-pack"
- "@yarnpkg/plugin-patch"
- "@yarnpkg/plugin-pnp"
- "@yarnpkg/plugin-pnpm"
- "@yarnpkg/plugin-stage"
- "@yarnpkg/plugin-typescript"
- "@yarnpkg/plugin-version"
- "@yarnpkg/plugin-workspace-tools"
- "@yarnpkg/builder"
- "@yarnpkg/core"
- "@yarnpkg/doctor"
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe(`Commands`, () => {
);

test(
`it should migrate old lockfiles by setting enableScripts to true when unset`,
`it should migrate old lockfiles by setting enableScripts and approvedGitRepositories when unset`,
makeTemporaryEnv({
dependencies: {
[`no-deps`]: `1.0.0`,
Expand Down Expand Up @@ -92,6 +92,7 @@ describe(`Commands`, () => {
await run(`install`);

await expect(xfs.readFilePromise(rcPath, `utf8`)).resolves.toContain(`enableScripts: true`);
await expect(xfs.readFilePromise(rcPath, `utf8`)).resolves.toMatch(/approvedGitRepositories:\r?\n\s*-\s*['"]?\*\*['"]?/);
}),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ describe(`Features`, () => {
// We don't care about this flag; in an actual attack,
// the hash would be correct
checksumBehavior: `ignore`,
approvedGitRepositories: [
`https://github.com/yarnpkg/util-deprecate.git`,
],
}, async ({path, run, source}) => {
await run(`add`, replacement);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ const TESTED_URLS = {
[`https://github.com/yarnpkg/util-deprecate.git#b3562c2798507869edb767da869cd7b85487726d`]: {version: `1.0.0`, runOnCI: true},
};

const defaultGitConfiguration = {
approvedGitRepositories: [
`http://localhost:*/repositories/*.git`,
`https://github.com/yarnpkg/util-deprecate.git`,
`ssh://git@github.com/yarnpkg/util-deprecate.git`,
],
};

describe(`Protocols`, () => {
describe(`git:`, () => {
for (const [url, {version, runOnCI}] of Object.entries(TESTED_URLS)) {
Expand All @@ -34,6 +42,7 @@ describe(`Protocols`, () => {
{
dependencies: {[`util-deprecate`]: url},
},
defaultGitConfiguration,
async ({path, run, source}) => {
await run(`install`);

Expand All @@ -54,6 +63,7 @@ describe(`Protocols`, () => {
[`has-prepack`]: tests.startPackageServer().then(url => `${url}/repositories/has-prepack.git`),
},
},
defaultGitConfiguration,
async ({path, run, source}) => {
await run(`install`);

Expand All @@ -70,6 +80,7 @@ describe(`Protocols`, () => {
[`no-prepack`]: tests.startPackageServer().then(url => `${url}/repositories/no-prepack.git`),
},
},
defaultGitConfiguration,
async ({path, run, source}) => {
await run(`install`);

Expand All @@ -87,6 +98,7 @@ describe(`Protocols`, () => {
[`pkg-b`]: tests.startPackageServer().then(url => `${url}/repositories/deep-projects.git#cwd=projects/pkg-b`),
},
},
defaultGitConfiguration,
async ({path, run, source}) => {
await run(`install`);

Expand All @@ -103,6 +115,25 @@ describe(`Protocols`, () => {
),
);

test(
`it should block git dependencies from repositories that aren't approved`,
makeTemporaryEnv(
{
dependencies: {
[`has-prepack`]: tests.startPackageServer().then(url => `${url}/repositories/has-prepack.git`),
},
},
{
approvedGitRepositories: [`https://github.com/yarnpkg/*`],
},
async ({run}) => {
await expect(run(`install`)).rejects.toThrow(
/doesn't match any of the patterns in 'approvedGitRepositories'/,
);
},
),
);

test(
`it should support installing workspace packages from projects in subfolders`,
makeTemporaryEnv(
Expand All @@ -112,6 +143,7 @@ describe(`Protocols`, () => {
[`lib-b`]: tests.startPackageServer().then(url => `${url}/repositories/deep-projects.git#cwd=projects/pkg-b&workspace=lib`),
},
},
defaultGitConfiguration,
async ({path, run, source}) => {
await run(`install`);

Expand Down Expand Up @@ -140,6 +172,7 @@ describe(`Protocols`, () => {
[`pkg-b`]: tests.startPackageServer().then(url => `${url}/repositories/workspaces.git#workspace=pkg-b`),
},
},
defaultGitConfiguration,
async ({path, run, source}) => {
await run(`install`);

Expand All @@ -164,6 +197,7 @@ describe(`Protocols`, () => {
[`yarn-1-project`]: tests.startPackageServer().then(url => `${url}/repositories/yarn-1-project.git`),
},
},
defaultGitConfiguration,
async ({path, run, source}) => {
await expect(run(`install`, {
env: {
Expand All @@ -188,6 +222,7 @@ describe(`Protocols`, () => {
[`npm-project`]: tests.startPackageServer().then(url => `${url}/repositories/npm-project.git`),
},
},
defaultGitConfiguration,
async ({path, run, source}) => {
await run(`install`);

Expand All @@ -204,6 +239,7 @@ describe(`Protocols`, () => {
[`npm-has-prepack`]: tests.startPackageServer().then(url => `${url}/repositories/npm-has-prepack.git`),
},
},
defaultGitConfiguration,
async ({path, run, source}) => {
await expect(run(`install`, {
env: {
Expand Down Expand Up @@ -236,6 +272,7 @@ describe(`Protocols`, () => {
[`pkg-b`]: tests.startPackageServer().then(url => `${url}/repositories/npm-workspaces.git#workspace=pkg-b`),
},
},
defaultGitConfiguration,
async ({path, run, source}) => {
const {code, stdout, stderr} = await execUtils.execvp(`npm`, [`--version`], {cwd: path});
if (code !== 0)
Expand Down Expand Up @@ -271,6 +308,7 @@ describe(`Protocols`, () => {
[`yarn-1-project`]: tests.startPackageServer().then(url => `${url}/repositories/yarn-1-project.git`),
},
},
defaultGitConfiguration,
async ({path, run, source}) => {
// This checks that the `set version classic` part of `scriptUtils.prepareExternalProject` doesn't use Corepack.
// The rest of the install will fail though.
Expand All @@ -295,6 +333,7 @@ describe(`Protocols`, () => {
[`no-lockfile-project`]: tests.startPackageServer().then(url => `${url}/repositories/no-lockfile-project.git`),
},
},
defaultGitConfiguration,
async ({path, run, source}) => {
await expect(run(`install`, {
env: {
Expand All @@ -314,6 +353,7 @@ describe(`Protocols`, () => {
[`yarn-1-project`]: tests.startPackageServer().then(url => `${url}/repositories/yarn-1-project.git`),
},
},
defaultGitConfiguration,
async ({path, run, source}) => {
await expect(run(`install`)).resolves.toBeTruthy();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ The `git:` protocol fetches packages directly from a git repository. This is use
yarn add typanion@git@github.com/arcanis/typanion.git
```

## Repository approval

Git dependencies are restricted through the `approvedGitRepositories` setting. GitHub repositories must match at least one of its glob patterns, otherwise Yarn will refuse to fetch them.

```yaml
approvedGitRepositories:
- https://github.com/yarnpkg/*
- ssh://git@github.com/yarnpkg/*
```

## Packing

The target repository won't be used as-is - it will first be packed using [`pack`](/cli/pack).
Expand Down
11 changes: 11 additions & 0 deletions packages/docusaurus/static/configuration/yarnrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@
"type": "number",
"default": 2
},
"approvedGitRepositories": {
"_package": "@yarnpkg/plugin-git",
"title": "Array of git repository URL glob patterns that are allowed to be fetched.",
"description": "When set, Yarn will block any git dependency whose normalized repository URL doesn't match one of these patterns. GitHub repositories must be explicitly approved.",
"type": "array",
"items": {
"type": "string"
},
"default": [],
"_exampleItems": ["https://github.com/yarnpkg/*", "ssh://git@github.com/yarnpkg/*"]
},
"compressionLevel": {
"_package": "@yarnpkg/core",
"type": ["number", "string"],
Expand Down
4 changes: 4 additions & 0 deletions packages/plugin-essentials/sources/commands/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const LOCKFILE_MIGRATION_RULES: Array<{
selector: v => v !== -1 && v < 8,
name: `compressionLevel`,
value: `mixed`,
}, {
selector: v => v < 9,
name: `approvedGitRepositories` as keyof ConfigurationValueMap,
value: [`**`],
}, {
selector: v => v < 9,
name: `enableScripts`,
Expand Down
12 changes: 11 additions & 1 deletion packages/plugin-git/sources/gitUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,21 @@ export function normalizeLocator(locator: Locator) {
}

export function validateRepoUrl(url: string, {configuration}: {configuration: Configuration}) {
const normalizedRepoUrl = normalizeRepoUrl(url, {git: true});
const {repo} = splitRepoUrl(url);
const normalizedRepoUrl = normalizeRepoUrl(repo, {git: true});

const networkSettings = httpUtils.getNetworkSettings(`https://${GitUrlParse(normalizedRepoUrl).resource}`, {configuration});
if (!networkSettings.enableNetwork)
throw new ReportError(MessageName.NETWORK_DISABLED, `Request to '${normalizedRepoUrl}' has been blocked because of your configuration settings`);

const approvedGitRepositoriesPattern = miscUtils.buildIgnorePattern(configuration.get(`approvedGitRepositories`));
if (approvedGitRepositoriesPattern === null || !normalizedRepoUrl.match(approvedGitRepositoriesPattern)) {
throw new ReportError(
MessageName.NETWORK_DISABLED,
`Request to '${normalizedRepoUrl}' has been blocked because it doesn't match any of the patterns in 'approvedGitRepositories'`,
);
}

return normalizedRepoUrl;
}

Expand Down
7 changes: 7 additions & 0 deletions packages/plugin-git/sources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface Hooks {

declare module '@yarnpkg/core' {
interface ConfigurationValueMap {
approvedGitRepositories: Array<string>;
changesetBaseRefs: Array<string>;
changesetIgnorePatterns: Array<string>;
cloneConcurrency: number;
Expand All @@ -32,6 +33,12 @@ declare module '@yarnpkg/core' {

const plugin: Plugin = {
configuration: {
approvedGitRepositories: {
description: `Array of git repository URL glob patterns that are allowed to be fetched`,
type: SettingsType.STRING,
default: [],
isArray: true,
},
changesetBaseRefs: {
description: `The base git refs that the current HEAD is compared against when detecting changes. Supports git branches, tags, and commits.`,
type: SettingsType.STRING,
Expand Down
Loading