Skip to content

Commit cf38af1

Browse files
fix: restricted scorecard-classic project url argument (#2649)
1 parent 117a2cf commit cf38af1

File tree

5 files changed

+43
-1
lines changed

5 files changed

+43
-1
lines changed

.changeset/dirty-walls-jam.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@redocly/cli": patch
3+
---
4+
5+
Restricted scorecard-classic project URL to the `.redocly.com` domain only.

docs/@v2/commands/scorecard-classic.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ redocly scorecard-classic openapi/openapi.yaml --config=./another/directory/redo
5454

5555
### Configure scorecard in redocly.yaml
5656

57-
You can configure the scorecard project URL in your Redocly configuration file to avoid passing it as a command-line argument:
57+
You can configure the scorecard project URL in your Redocly configuration file to avoid passing it as a command-line argument. The URL must use a hostname that ends with `.redocly.com` (for example, `app.cloud.redocly.com`), other domains are not allowed.
5858

5959
```yaml
6060
scorecard:
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { isAllowedScorecardProjectUrl } from '../validation/project-url.js';
2+
3+
describe('isAllowedScorecardProjectUrl', () => {
4+
it('should return true for valid project URLs', () => {
5+
expect(
6+
isAllowedScorecardProjectUrl('https://app.some.redocly.com/org/my-org/project/my-project')
7+
).toBe(true);
8+
});
9+
10+
it('should return false for invalid project URLs', () => {
11+
expect(isAllowedScorecardProjectUrl('https://example.com/org/my-org/project/my-project')).toBe(
12+
false
13+
);
14+
expect(isAllowedScorecardProjectUrl('file://example/org/my-org/project/my-project')).toBe(
15+
false
16+
);
17+
expect(
18+
isAllowedScorecardProjectUrl('https://app.some.remocly.com/org/my-org/project/my-project')
19+
).toBe(false);
20+
expect(isAllowedScorecardProjectUrl('project/my-project')).toBe(false);
21+
});
22+
});

packages/cli/src/commands/scorecard-classic/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { printScorecardResultsAsJson } from './formatters/json-formatter.js';
1010
import { printScorecardResults } from './formatters/stylish-formatter.js';
1111
import { fetchRemoteScorecardAndPlugins } from './remote/fetch-scorecard.js';
1212
import type { ScorecardClassicArgv } from './types.js';
13+
import { isAllowedScorecardProjectUrl } from './validation/project-url.js';
1314
import { validateScorecard } from './validation/validate-scorecard.js';
1415

1516
export async function handleScorecardClassic({
@@ -46,6 +47,10 @@ export async function handleScorecardClassic({
4647
);
4748
}
4849

50+
if (!isAllowedScorecardProjectUrl(projectUrl)) {
51+
exitWithError(`Project URL must be from the .redocly.com domain. Received: ${projectUrl}`);
52+
}
53+
4954
if (isNonInteractiveEnvironment() && !apiKey) {
5055
exitWithError(
5156
'Please provide an API key using the REDOCLY_AUTHORIZATION environment variable.\n'
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const ALLOWED_DOMAIN_SUFFIX = '.redocly.com';
2+
3+
export function isAllowedScorecardProjectUrl(urlString: string): boolean {
4+
try {
5+
const hostname = new URL(urlString).hostname.toLowerCase();
6+
return hostname.endsWith(ALLOWED_DOMAIN_SUFFIX);
7+
} catch {
8+
return false;
9+
}
10+
}

0 commit comments

Comments
 (0)