Skip to content

Commit 881023b

Browse files
fix(threat-intel): resolve list_sources URLs from the code catalog
After elastic#287201, feed URLs are no longer stored on source documents. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 1f76a38 commit 881023b

2 files changed

Lines changed: 16 additions & 35 deletions

File tree

x-pack/solutions/security/plugins/security_solution/server/threat_intel/routes/list_sources.test.ts

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -154,40 +154,22 @@ describe('loadSourceReportStatsByAdapterId', () => {
154154
// ── Credential exposure ──────────────────────────────────────────────────────
155155

156156
describe('mapSourceHit', () => {
157-
// This route is gated on Security Read and lists global sources in every space,
158-
// so returning the raw config URL handed feed credentials to anyone who could
159-
// read the catalog.
160-
it('redacts credentials from the returned URL', () => {
157+
it('returns the catalog URL for a known source id', () => {
161158
const mapped = mapSourceHitForTest({
162-
_id: 'default:rss:acme',
159+
_id: 'vendor_api:elastic-security-labs',
163160
_source: {
164-
name: 'Acme',
161+
name: 'Elastic Security Labs',
165162
adapter_type: 'rss',
166-
config: { url: 'https://feeduser:s3cret@feeds.example/rss.xml' },
167163
},
168164
});
169165

170-
expect(mapped.url).toBe('https://feeds.example/rss.xml');
171-
expect(JSON.stringify(mapped)).not.toContain('s3cret');
166+
expect(mapped.url).toBe('https://www.elastic.co/security-labs/rss/feed.xml');
172167
});
173168

174-
it('leaves a credential-free URL alone', () => {
169+
it('omits the url when the source id is outside the catalog', () => {
175170
const mapped = mapSourceHitForTest({
176-
_id: 'default:rss:acme',
177-
_source: {
178-
name: 'Acme',
179-
adapter_type: 'rss',
180-
config: { url: 'https://feeds.example/rss.xml' },
181-
},
182-
});
183-
184-
expect(mapped.url).toBe('https://feeds.example/rss.xml');
185-
});
186-
187-
it('omits the url when the source has none', () => {
188-
const mapped = mapSourceHitForTest({
189-
_id: 'default:kev:cisa',
190-
_source: { name: 'CISA', adapter_type: 'kev', config: {} },
171+
_id: 'rss:unknown',
172+
_source: { name: 'Acme', adapter_type: 'rss' },
191173
});
192174

193175
expect(mapped.url).toBeUndefined();
@@ -197,7 +179,7 @@ describe('mapSourceHit', () => {
197179
describe('loadSourceForMutation', () => {
198180
const globalSource = (spaceId?: string) => ({
199181
get: jest.fn().mockResolvedValue({
200-
_source: { name: 'Acme', adapter_type: 'rss', config: {}, space_id: spaceId },
182+
_source: { name: 'Acme', adapter_type: 'rss', space_id: spaceId },
201183
}),
202184
});
203185

x-pack/solutions/security/plugins/security_solution/server/threat_intel/routes/list_sources.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
THREAT_INTEL_SOURCES_INDEX,
1414
THREAT_REPORTS_INDEX_PATTERN,
1515
APPROVED_SOURCE_IDS,
16+
resolveCatalogSourceUrl,
1617
} from '../../../common/threat_intel';
1718
import {
1819
buildSpaceFilterTerms,
@@ -40,7 +41,6 @@ interface ThreatIntelSourceDoc {
4041
name?: string;
4142
adapter_type?: string;
4243
enabled?: boolean;
43-
config?: { url?: unknown };
4444
tags?: string[];
4545
created_at?: string;
4646
updated_at?: string;
@@ -76,18 +76,17 @@ const mapSourceHit = (hit: {
7676
_source?: ThreatIntelSourceDoc;
7777
}): Omit<ListSourcesItem, 'report_count' | 'last_ingested_at' | 'env_hits_total'> => {
7878
const source = hit._source ?? {};
79-
// Redacted for display. Feed URLs can embed `user:password@`, and this route is
80-
// gated on Security Read and lists global sources in every space, so returning
81-
// the raw value handed feed credentials to anyone who could read the catalog.
82-
// The stored document keeps the real URL for the adapter to fetch with.
83-
const configUrl =
84-
typeof source.config?.url === 'string' ? redactUrl(source.config.url) : undefined;
79+
const sourceId = hit._id ?? '';
80+
// URLs come from the code catalog, not Elasticsearch. Redact userinfo before
81+
// returning a URL to Security Read callers listing global sources in every space.
82+
const catalogUrl = resolveCatalogSourceUrl(sourceId);
83+
const displayUrl = typeof catalogUrl === 'string' ? redactUrl(catalogUrl) : undefined;
8584
return {
86-
source_id: hit._id ?? '',
85+
source_id: sourceId,
8786
name: source.name,
8887
adapter_type: source.adapter_type,
8988
enabled: source.enabled,
90-
...(typeof configUrl === 'string' ? { url: configUrl } : {}),
89+
...(typeof displayUrl === 'string' ? { url: displayUrl } : {}),
9190
tags: source.tags,
9291
created_at: source.created_at,
9392
updated_at: source.updated_at,

0 commit comments

Comments
 (0)