@adobe/spacecat-shared-data-access 3.70.2 → 3.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/models/audit/audit.model.js +38 -11
- package/src/models/site/config.js +75 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [@adobe/spacecat-shared-data-access-v3.71.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.70.2...@adobe/spacecat-shared-data-access-v3.71.0) (2026-05-25)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* add scraperConfig to SiteConfig ([#1618](https://github.com/adobe/spacecat-shared/issues/1618)) ([8e3707b](https://github.com/adobe/spacecat-shared/commit/8e3707b505a2834c9351c50a965d838b3bad6123))
|
|
6
|
+
|
|
1
7
|
## [@adobe/spacecat-shared-data-access-v3.70.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.70.1...@adobe/spacecat-shared-data-access-v3.70.2) (2026-05-23)
|
|
2
8
|
|
|
3
9
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -173,14 +173,28 @@ class Audit extends BaseModel {
|
|
|
173
173
|
getQueueUrl: (context) => context.env?.CONTENT_SCRAPER_QUEUE_URL,
|
|
174
174
|
/**
|
|
175
175
|
* Formats the payload for the content scraper queue.
|
|
176
|
+
*
|
|
177
|
+
* Per-site scraper headers (configured via the site's `scraperConfig.headers`)
|
|
178
|
+
* are auto-injected into the SQS payload's `customHeaders`, reusing the
|
|
179
|
+
* `site` already loaded by the audit framework on the context — no extra
|
|
180
|
+
* Site.findById is performed. Steps that need a different value can set
|
|
181
|
+
* `stepResult.customHeaders` explicitly to forward it verbatim instead.
|
|
182
|
+
*
|
|
176
183
|
* @param {object} stepResult - The result of the audit step.
|
|
177
184
|
* @param {object[]} stepResult.urls - The list of URLs to scrape.
|
|
178
185
|
* @param {string} stepResult.urls[].url - The URL to scrape.
|
|
179
186
|
* @param {string} stepResult.siteId - The site ID. Will be used as the job ID.
|
|
180
187
|
* @param {string} stepResult.options - The options for the scraper.
|
|
181
188
|
* @param {string} stepResult.processingType - The scraping processing type to trigger.
|
|
189
|
+
* @param {Object<string,string>} [stepResult.customHeaders] - Explicit
|
|
190
|
+
* override for the HTTP headers forwarded to the scraper. When unset,
|
|
191
|
+
* the dispatcher auto-loads from `context.site.getConfig().getScraperConfig()?.headers`.
|
|
182
192
|
* @param {object} auditContext - The audit context.
|
|
183
|
-
* @param {object} context - The context object.
|
|
193
|
+
* @param {object} context - The context object. `context.site` is the
|
|
194
|
+
* loaded site model when available; the auto-load uses optional
|
|
195
|
+
* chaining so older code paths that have not yet attached a site to
|
|
196
|
+
* context (or sites whose Config predates the scraperConfig getter)
|
|
197
|
+
* degrade to "no headers" rather than throwing.
|
|
184
198
|
* @param {object} auditContext.next - The next audit step to run.
|
|
185
199
|
* @param {string} auditContext.auditId - The audit ID.
|
|
186
200
|
* @param {string} auditContext.auditType - The audit type.
|
|
@@ -188,16 +202,29 @@ class Audit extends BaseModel {
|
|
|
188
202
|
*
|
|
189
203
|
* @returns {object} - The formatted payload.
|
|
190
204
|
*/
|
|
191
|
-
formatPayload: (stepResult, auditContext, context) =>
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
formatPayload: (stepResult, auditContext, context) => {
|
|
206
|
+
const payload = {
|
|
207
|
+
urls: stepResult.urls,
|
|
208
|
+
jobId: stepResult.siteId,
|
|
209
|
+
processingType: stepResult.processingType || 'default',
|
|
210
|
+
skipMessage: false,
|
|
211
|
+
allowCache: isBoolean(stepResult.allowCache) ? stepResult.allowCache : true,
|
|
212
|
+
options: stepResult.options || {},
|
|
213
|
+
completionQueueUrl: stepResult.completionQueueUrl || context.env?.AUDIT_JOBS_QUEUE_URL,
|
|
214
|
+
auditContext,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Prefer step-supplied customHeaders; otherwise auto-load from site config.
|
|
218
|
+
const customHeaders = stepResult.customHeaders
|
|
219
|
+
?? context?.site?.getConfig?.()?.getScraperConfig?.()?.headers;
|
|
220
|
+
|
|
221
|
+
// Reject empty object so the scraper does not receive `customHeaders: {}`.
|
|
222
|
+
if (customHeaders && Object.keys(customHeaders).length > 0) {
|
|
223
|
+
payload.customHeaders = customHeaders;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return payload;
|
|
227
|
+
},
|
|
201
228
|
},
|
|
202
229
|
[Audit.AUDIT_STEP_DESTINATIONS.SCRAPE_CLIENT]: {
|
|
203
230
|
/**
|
|
@@ -46,6 +46,28 @@ export const IMPORT_SOURCES = {
|
|
|
46
46
|
RUM: 'rum',
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
// HTTP header names that must not be set via `scraperConfig.headers`
|
|
50
|
+
// (case-insensitive). These are owned by the scraper, its HTTP client, or its
|
|
51
|
+
// auth flow; persisting them as per-site overrides would either be stripped at
|
|
52
|
+
// scrape time or open a security surface outside the per-site config contract.
|
|
53
|
+
const RESERVED_SCRAPER_HEADER_NAMES = new Set([
|
|
54
|
+
// Credential / authentication headers.
|
|
55
|
+
'authorization',
|
|
56
|
+
'cookie',
|
|
57
|
+
'proxy-authorization',
|
|
58
|
+
// Routing / fingerprint headers the scraper or its environment owns.
|
|
59
|
+
'host',
|
|
60
|
+
'user-agent',
|
|
61
|
+
// Hop-by-hop / connection-management headers (RFC 7230 section 6.1).
|
|
62
|
+
'content-length',
|
|
63
|
+
'transfer-encoding',
|
|
64
|
+
'connection',
|
|
65
|
+
'keep-alive',
|
|
66
|
+
'upgrade',
|
|
67
|
+
'te',
|
|
68
|
+
'trailer',
|
|
69
|
+
]);
|
|
70
|
+
|
|
49
71
|
const LLMO_TAG_PATTERN = /^(market|product|topic):\s?.+/;
|
|
50
72
|
const AWS_REGION_PATTERN = /^[a-z]{2}(?:-[a-z]+)+-\d+$/i;
|
|
51
73
|
const LLMO_TAG = Joi.alternatives()
|
|
@@ -379,6 +401,42 @@ export const configSchema = Joi.object({
|
|
|
379
401
|
).optional(),
|
|
380
402
|
outputLocation: Joi.string().required(),
|
|
381
403
|
}).optional(),
|
|
404
|
+
/**
|
|
405
|
+
* Per-site configuration intended to be forwarded by the content scraper as
|
|
406
|
+
* HTTP headers on outbound requests. Values are bounded and restricted to
|
|
407
|
+
* printable ASCII to avoid header-injection (CR/LF) and oversized-payload
|
|
408
|
+
* hazards. Header names are restricted to RFC 7230 token characters.
|
|
409
|
+
* Reserved names (see `RESERVED_SCRAPER_HEADER_NAMES`) are rejected.
|
|
410
|
+
*
|
|
411
|
+
* Enforcement lives at the schema layer (rather than at each consumer or API
|
|
412
|
+
* boundary) so any writer using `updateScraperConfig` -- API endpoint,
|
|
413
|
+
* internal tool, Slack command -- inherits the same checks.
|
|
414
|
+
*/
|
|
415
|
+
scraperConfig: Joi.object({
|
|
416
|
+
headers: Joi.object()
|
|
417
|
+
.pattern(
|
|
418
|
+
// RFC 7230 token characters for header names, 64 chars max.
|
|
419
|
+
Joi.string().pattern(/^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$/).max(64),
|
|
420
|
+
// Visible ASCII + tab, no CR/LF/NUL, 1 to 1024 chars (empty rejected).
|
|
421
|
+
Joi.string().pattern(/^[\t\x20-\x7E]+$/).max(1024),
|
|
422
|
+
)
|
|
423
|
+
.max(32)
|
|
424
|
+
// Case-insensitive denylist for credential / routing / hop-by-hop names.
|
|
425
|
+
// Done as an object-level custom validator (rather than on the key
|
|
426
|
+
// pattern) so the error message names the offending header instead of
|
|
427
|
+
// surfacing as Joi's generic "is not allowed" pattern-object error.
|
|
428
|
+
.custom((value, helpers) => {
|
|
429
|
+
for (const key of Object.keys(value)) {
|
|
430
|
+
if (RESERVED_SCRAPER_HEADER_NAMES.has(key.toLowerCase())) {
|
|
431
|
+
return helpers.message(
|
|
432
|
+
`"${key}" is a reserved scraper header name and cannot be set via scraperConfig.headers`,
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return value;
|
|
437
|
+
})
|
|
438
|
+
.optional(),
|
|
439
|
+
}).optional(),
|
|
382
440
|
tokowakaConfig: Joi.object({
|
|
383
441
|
apiKey: Joi.string().optional(),
|
|
384
442
|
forwardedHost: Joi.string().optional(),
|
|
@@ -529,6 +587,7 @@ export const Config = (data = {}) => {
|
|
|
529
587
|
self.getBrandConfig = () => state?.brandConfig;
|
|
530
588
|
self.getBrandProfile = () => state?.brandProfile;
|
|
531
589
|
self.getCdnLogsConfig = () => state?.cdnLogsConfig;
|
|
590
|
+
self.getScraperConfig = () => state?.scraperConfig;
|
|
532
591
|
self.getLlmoConfig = () => state?.llmo;
|
|
533
592
|
self.getLlmoDataFolder = () => state?.llmo?.dataFolder;
|
|
534
593
|
self.getLlmoBrand = () => state?.llmo?.brand;
|
|
@@ -949,6 +1008,21 @@ export const Config = (data = {}) => {
|
|
|
949
1008
|
state.cdnLogsConfig = cdnLogsConfig;
|
|
950
1009
|
};
|
|
951
1010
|
|
|
1011
|
+
self.updateScraperConfig = (scraperConfig) => {
|
|
1012
|
+
// Validate eagerly (unlike neighboring setters) so bad input is rejected
|
|
1013
|
+
// at the setter rather than silently round-tripping through Dynamo. This
|
|
1014
|
+
// eagerness is the contract every writer relies on for scraperConfig;
|
|
1015
|
+
// do not "harmonize" with cdnLogs/tokowaka setters.
|
|
1016
|
+
//
|
|
1017
|
+
// Use the sanitized value Joi returns so any future `default()` or
|
|
1018
|
+
// `lowercase()` on the schema applies here too — assigning raw input
|
|
1019
|
+
// would silently drift from what validation guarantees.
|
|
1020
|
+
const { scraperConfig: validated } = validateConfiguration(
|
|
1021
|
+
{ ...state, scraperConfig },
|
|
1022
|
+
);
|
|
1023
|
+
state.scraperConfig = validated;
|
|
1024
|
+
};
|
|
1025
|
+
|
|
952
1026
|
self.updateTokowakaConfig = (tokowakaConfig) => {
|
|
953
1027
|
state.tokowakaConfig = tokowakaConfig;
|
|
954
1028
|
};
|
|
@@ -1003,6 +1077,7 @@ Config.toDynamoItem = (config) => ({
|
|
|
1003
1077
|
brandConfig: config.getBrandConfig(),
|
|
1004
1078
|
brandProfile: config.getBrandProfile(),
|
|
1005
1079
|
cdnLogsConfig: config.getCdnLogsConfig(),
|
|
1080
|
+
scraperConfig: config.getScraperConfig(),
|
|
1006
1081
|
llmo: config.getLlmoConfig(),
|
|
1007
1082
|
tokowakaConfig: config.getTokowakaConfig(),
|
|
1008
1083
|
edgeOptimizeConfig: config.getEdgeOptimizeConfig(),
|