Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .bumpy/fix-url-allowed-domains.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
varlock: patch
---

Fix url(allowedDomains) comma-string membership checks
16 changes: 12 additions & 4 deletions packages/varlock/src/env-graph/lib/data-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,10 +404,18 @@ const UrlDataType = createEnvGraphDataType(
throw new ValidationError('Invalid URL');
}
const errors = [] as Array<ValidationError>;
if (
settings?.allowedDomains && !settings.allowedDomains.includes(url.host.toLowerCase())
) {
errors.push(new ValidationError(`Domain (${url.host}) is not in allowed list: ${settings.allowedDomains.join(',')}`));
// allowedDomains may arrive as a comma-string (`"a.com,b.com"`) from schema
// syntax, or as a real array. Normalize before membership checks — string
// `.includes` is substring match and would allow e.g. "ample.com" for "example.com",
// and `.join` on a string throws when building the rejection message.
const allowedDomains = (() => {
const raw = settings?.allowedDomains as Array<string> | string | undefined;
if (!raw) return [] as Array<string>;
const list = Array.isArray(raw) ? raw : String(raw).split(',');
return list.map((d) => d.trim().toLowerCase()).filter(Boolean);
})();
if (allowedDomains.length && !allowedDomains.includes(url.host.toLowerCase())) {
errors.push(new ValidationError(`Domain (${url.host}) is not in allowed list: ${allowedDomains.join(',')}`));
}
if (settings?.noTrailingSlash && val.endsWith('/')) {
errors.push(new ValidationError('URL must not have a trailing slash'));
Expand Down
28 changes: 28 additions & 0 deletions packages/varlock/src/env-graph/test/data-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,34 @@ describe('url data type', () => {
});
});

describe('allowedDomains', () => {
it('accepts a host listed in a comma-string allowlist', async () => {
const g = await loadAndResolve(outdent`
# @type=url(allowedDomains="example.com,api.example.com")
MY_URL=https://api.example.com/v1
`);
expect(g.configSchema.MY_URL.isValid).toBe(true);
});

it('rejects a host that is only a substring of an allowlist entry', async () => {
const g = await loadAndResolve(outdent`
# @type=url(allowedDomains="example.com")
MY_URL=https://ample.com/
`);
expect(g.configSchema.MY_URL.isValid).toBe(false);
expect(g.configSchema.MY_URL.errors[0]?.message).toMatch(/not in allowed list/);
});

it('rejects a disallowed host without throwing on the error message', async () => {
const g = await loadAndResolve(outdent`
# @type=url(allowedDomains="example.com")
MY_URL=https://evil.com/
`);
expect(g.configSchema.MY_URL.isValid).toBe(false);
expect(g.configSchema.MY_URL.errors[0]?.message).toContain('example.com');
});
});

describe('noTrailingSlash', () => {
it('accepts url without trailing slash', async () => {
const g = await loadAndResolve(outdent`
Expand Down
Loading