@promptbook/cli 0.112.0-131 → 0.112.0-132
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/apps/agents-server/src/app/admin/custom-js/CustomJsClient.tsx +7 -0
- package/apps/agents-server/src/app/layout.tsx +12 -3
- package/apps/agents-server/src/components/Footer/Footer.tsx +3 -2
- package/apps/agents-server/src/components/Footer/getCommitFooterEmoji.ts +112 -0
- package/apps/agents-server/src/languages/translations/czech.yaml +1 -1
- package/apps/agents-server/src/languages/translations/english.yaml +1 -1
- package/apps/agents-server/src/middleware/applyEmbeddingHeader.ts +4 -2
- package/apps/agents-server/src/middleware/contentSecurityPolicy.ts +97 -0
- package/esm/index.es.js +1 -1
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/other/templates/getTemplatesPipelineCollection.ts +764 -769
- package/src/version.ts +2 -2
- package/src/versions.txt +1 -0
- package/umd/index.umd.js +1 -1
- package/umd/src/version.d.ts +1 -1
|
@@ -53,6 +53,13 @@ export function CustomJsClient() {
|
|
|
53
53
|
Scripts entered here are concatenated and injected on every Agents Server page. Use them to add helpers,
|
|
54
54
|
integrations, or instrumentation, but keep them lean since runtime errors can impact the UI.
|
|
55
55
|
</p>
|
|
56
|
+
<p className="text-sm text-gray-600 mb-6">
|
|
57
|
+
Scripts run inside a strict Content Security Policy: they are loaded via a per-request{' '}
|
|
58
|
+
<code>nonce</code> on the inline <code><script></code> tag, and any extra scripts they create at
|
|
59
|
+
runtime inherit that trust via <code>script-src 'strict-dynamic'</code>. Closing{' '}
|
|
60
|
+
<code></script></code> sequences are no longer rewritten — write valid JavaScript and avoid raw
|
|
61
|
+
HTML payloads here.
|
|
62
|
+
</p>
|
|
56
63
|
|
|
57
64
|
{error && (
|
|
58
65
|
<div className="mb-4 rounded-md border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-700">
|
|
@@ -3,7 +3,8 @@ import { LayoutWrapper } from '@/src/components/LayoutWrapper/LayoutWrapper';
|
|
|
3
3
|
import { createThemeModeBootstrapScript } from '@/src/components/ThemeMode/createThemeModeBootstrapScript';
|
|
4
4
|
import { APPLICATION_FONT_VARIABLE_CLASS_NAME } from '@/src/utils/applicationFonts';
|
|
5
5
|
import type { Metadata } from 'next';
|
|
6
|
-
import { cookies } from 'next/headers';
|
|
6
|
+
import { cookies, headers } from 'next/headers';
|
|
7
|
+
import { CONTENT_SECURITY_POLICY_NONCE_REQUEST_HEADER } from '../middleware/contentSecurityPolicy';
|
|
7
8
|
import { getCustomJavascriptWithIntegrations } from '../database/customJavascript';
|
|
8
9
|
import { getAggregatedCustomStylesheetCss } from '../database/customStylesheet';
|
|
9
10
|
import { getMetadataMap } from '../database/getMetadata';
|
|
@@ -293,6 +294,7 @@ export default async function RootLayout({
|
|
|
293
294
|
getCustomJavascriptWithIntegrations,
|
|
294
295
|
);
|
|
295
296
|
const cookieStorePromise = cookies();
|
|
297
|
+
const requestHeadersPromise = headers();
|
|
296
298
|
const defaultThemeModePromise = Promise.all([currentUserPromise, cookieStorePromise, layoutMetadataPromise]).then(
|
|
297
299
|
async ([currentUser, cookieStore, layoutMetadata]) => {
|
|
298
300
|
const cookieThemeMode = cookieStore.get(THEME_MODE_COOKIE_NAME)?.value || null;
|
|
@@ -346,6 +348,7 @@ export default async function RootLayout({
|
|
|
346
348
|
customStylesheetCss,
|
|
347
349
|
customJavascript,
|
|
348
350
|
cookieStore,
|
|
351
|
+
requestHeaders,
|
|
349
352
|
defaultThemeMode,
|
|
350
353
|
federatedServers,
|
|
351
354
|
footerLinks,
|
|
@@ -363,6 +366,7 @@ export default async function RootLayout({
|
|
|
363
366
|
customStylesheetCssPromise,
|
|
364
367
|
customJavascriptPromise,
|
|
365
368
|
cookieStorePromise,
|
|
369
|
+
requestHeadersPromise,
|
|
366
370
|
defaultThemeModePromise,
|
|
367
371
|
federatedServersPromise,
|
|
368
372
|
footerLinksPromise,
|
|
@@ -379,7 +383,10 @@ export default async function RootLayout({
|
|
|
379
383
|
const feedbackMode = parseChatFeedbackMode(layoutMetadata.CHAT_FEEDBACK_MODE, layoutMetadata.IS_FEEDBACK_ENABLED);
|
|
380
384
|
const isExperimentalPwaAppEnabled = (layoutMetadata.IS_EXPERIMENTAL_PWA_APP_ENABLED ?? 'true') === 'true';
|
|
381
385
|
const isPublicServer = isPublicServerVisibility(serverVisibility);
|
|
382
|
-
|
|
386
|
+
// Note: The custom JavaScript is executed only when the browser sees the matching
|
|
387
|
+
// `script-src 'nonce-...'` directive that the middleware sets per request, so a
|
|
388
|
+
// fragile string-level sanitization is intentionally not applied here.
|
|
389
|
+
const contentSecurityPolicyNonce = requestHeaders.get(CONTENT_SECURITY_POLICY_NONCE_REQUEST_HEADER) ?? undefined;
|
|
383
390
|
const webPushPublicKey = process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY?.trim() || null;
|
|
384
391
|
const chatVisualModeCookie = cookieStore.get(CHAT_VISUAL_MODE_COOKIE_NAME)?.value || null;
|
|
385
392
|
const cookieLanguage = cookieStore.get(SERVER_LANGUAGE_COOKIE_NAME)?.value || null;
|
|
@@ -412,6 +419,7 @@ export default async function RootLayout({
|
|
|
412
419
|
<body className={`${APPLICATION_FONT_VARIABLE_CLASS_NAME} bg-background text-foreground antialiased`}>
|
|
413
420
|
<script
|
|
414
421
|
id="agents-server-theme-mode"
|
|
422
|
+
nonce={contentSecurityPolicyNonce}
|
|
415
423
|
dangerouslySetInnerHTML={{ __html: themeModeBootstrapScript }}
|
|
416
424
|
/>
|
|
417
425
|
{customStylesheetCss && <style id="agents-server-custom-css">{customStylesheetCss}</style>}
|
|
@@ -448,7 +456,8 @@ export default async function RootLayout({
|
|
|
448
456
|
{customJavascript && (
|
|
449
457
|
<script
|
|
450
458
|
id="agents-server-custom-js"
|
|
451
|
-
|
|
459
|
+
nonce={contentSecurityPolicyNonce}
|
|
460
|
+
dangerouslySetInnerHTML={{ __html: customJavascript }}
|
|
452
461
|
/>
|
|
453
462
|
)}
|
|
454
463
|
{/* Global portal root for modals/popups */}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF } from '@/config';
|
|
3
|
+
import { NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF, NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA } from '@/config';
|
|
4
4
|
import { CLAIM, NAME, PROMPTBOOK_ENGINE_VERSION } from '@promptbook-local/core';
|
|
5
5
|
import { useServerLanguage } from '../ServerLanguage/ServerLanguageProvider';
|
|
6
6
|
import { HeadlessLink } from '../_utils/headlessParam';
|
|
7
|
+
import { getCommitFooterEmoji } from './getCommitFooterEmoji';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Title/url pair used to extend the footer via metadata.
|
|
@@ -169,7 +170,7 @@ export function Footer(props: FooterProps) {
|
|
|
169
170
|
<br />
|
|
170
171
|
{t('footer.allRightsReserved')}
|
|
171
172
|
<br />
|
|
172
|
-
{t('footer.madeInCzechRepublic')}
|
|
173
|
+
{t('footer.madeInCzechRepublic', { emoji: getCommitFooterEmoji(NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA) })}
|
|
173
174
|
{/* <- TODO: !!!!!!!! Put here Prague outline */}
|
|
174
175
|
</p>
|
|
175
176
|
<p className="mt-2 text-xs text-gray-400">
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curated list of positive or neutral emojis displayed in the footer's "Made with {emoji} in Europe" line.
|
|
3
|
+
*
|
|
4
|
+
* The order of this list is part of the deterministic mapping from commit SHA to emoji, so existing
|
|
5
|
+
* entries should not be reordered. New emojis can be appended at the end without affecting the
|
|
6
|
+
* indexes of existing ones, but each commit SHA will then map to a different emoji.
|
|
7
|
+
*
|
|
8
|
+
* Hearts, cute animals, plants, fruits, sweets, and celebratory symbols are intentionally chosen so
|
|
9
|
+
* that the footer never shows anything aggressive, scary, or negative.
|
|
10
|
+
*/
|
|
11
|
+
const POSITIVE_FOOTER_EMOJIS = [
|
|
12
|
+
'❤️',
|
|
13
|
+
'🧡',
|
|
14
|
+
'💛',
|
|
15
|
+
'💚',
|
|
16
|
+
'💙',
|
|
17
|
+
'💜',
|
|
18
|
+
'🤍',
|
|
19
|
+
'💖',
|
|
20
|
+
'💝',
|
|
21
|
+
'🐙',
|
|
22
|
+
'🦄',
|
|
23
|
+
'🐶',
|
|
24
|
+
'🐱',
|
|
25
|
+
'🐰',
|
|
26
|
+
'🦊',
|
|
27
|
+
'🐻',
|
|
28
|
+
'🐼',
|
|
29
|
+
'🐨',
|
|
30
|
+
'🐯',
|
|
31
|
+
'🦁',
|
|
32
|
+
'🐸',
|
|
33
|
+
'🐵',
|
|
34
|
+
'🐧',
|
|
35
|
+
'🦉',
|
|
36
|
+
'🐢',
|
|
37
|
+
'🦋',
|
|
38
|
+
'🐝',
|
|
39
|
+
'🐞',
|
|
40
|
+
'🐳',
|
|
41
|
+
'🐬',
|
|
42
|
+
'🦒',
|
|
43
|
+
'🐘',
|
|
44
|
+
'🌸',
|
|
45
|
+
'🌻',
|
|
46
|
+
'🌷',
|
|
47
|
+
'🌹',
|
|
48
|
+
'🌈',
|
|
49
|
+
'🌟',
|
|
50
|
+
'✨',
|
|
51
|
+
'☀️',
|
|
52
|
+
'🌙',
|
|
53
|
+
'🍀',
|
|
54
|
+
'🌳',
|
|
55
|
+
'🍎',
|
|
56
|
+
'🍓',
|
|
57
|
+
'🍑',
|
|
58
|
+
'🍒',
|
|
59
|
+
'🍋',
|
|
60
|
+
'🍊',
|
|
61
|
+
'🍇',
|
|
62
|
+
'🍉',
|
|
63
|
+
'🍰',
|
|
64
|
+
'🍪',
|
|
65
|
+
'🍩',
|
|
66
|
+
'🍕',
|
|
67
|
+
'🍔',
|
|
68
|
+
'🍦',
|
|
69
|
+
'🍫',
|
|
70
|
+
'🎉',
|
|
71
|
+
'🎈',
|
|
72
|
+
'🎁',
|
|
73
|
+
'🎨',
|
|
74
|
+
'🚀',
|
|
75
|
+
'⭐',
|
|
76
|
+
'💎',
|
|
77
|
+
'🪐',
|
|
78
|
+
'🎵',
|
|
79
|
+
] as const;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Default emoji used in the footer when no commit SHA is available (for example during local
|
|
83
|
+
* development outside Vercel). Kept as the historic `❤️` so the footer text does not look
|
|
84
|
+
* unusual when the deployment metadata is missing.
|
|
85
|
+
*/
|
|
86
|
+
const DEFAULT_FOOTER_EMOJI: string = POSITIVE_FOOTER_EMOJIS[0]!;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Derives a stable emoji from a commit SHA using a small djb2-style string hash.
|
|
90
|
+
*
|
|
91
|
+
* The same SHA always produces the same emoji, so users can spot a server update by noticing that
|
|
92
|
+
* the footer emoji changed. The result is taken modulo the curated list, so it is always one of
|
|
93
|
+
* the positive/neutral emojis in {@link POSITIVE_FOOTER_EMOJIS}.
|
|
94
|
+
*
|
|
95
|
+
* @param commitSha - Full or short commit SHA. May be `null` or `undefined` when running outside
|
|
96
|
+
* a Vercel deployment, in which case {@link DEFAULT_FOOTER_EMOJI} is returned.
|
|
97
|
+
* @returns One emoji from {@link POSITIVE_FOOTER_EMOJIS}.
|
|
98
|
+
* @private Internal to `apps/agents-server`
|
|
99
|
+
*/
|
|
100
|
+
export function getCommitFooterEmoji(commitSha: string | null | undefined): string {
|
|
101
|
+
if (!commitSha) {
|
|
102
|
+
return DEFAULT_FOOTER_EMOJI;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let hash = 0;
|
|
106
|
+
for (let characterIndex = 0; characterIndex < commitSha.length; characterIndex++) {
|
|
107
|
+
hash = (hash * 31 + commitSha.charCodeAt(characterIndex)) | 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const emojiIndex = Math.abs(hash) % POSITIVE_FOOTER_EMOJIS.length;
|
|
111
|
+
return POSITIVE_FOOTER_EMOJIS[emojiIndex]!;
|
|
112
|
+
}
|
|
@@ -380,7 +380,7 @@ footer.logosAndBranding: Loga a vizuální identita
|
|
|
380
380
|
footer.connectSectionTitle: Spojte se s námi
|
|
381
381
|
footer.linksSectionTitle: Odkazy
|
|
382
382
|
footer.allRightsReserved: Všechna práva vyhrazena.
|
|
383
|
-
footer.madeInCzechRepublic: Vytvořeno s
|
|
383
|
+
footer.madeInCzechRepublic: Vytvořeno s {emoji} v České republice.
|
|
384
384
|
footer.engineVersion: Verze jádra Promptbooku
|
|
385
385
|
forbidden.title: 403 Zakázáno
|
|
386
386
|
forbidden.message: Nemáte oprávnění k přístupu na tuto stránku.
|
|
@@ -384,7 +384,7 @@ footer.logosAndBranding: Logos & Branding
|
|
|
384
384
|
footer.connectSectionTitle: Connect
|
|
385
385
|
footer.linksSectionTitle: Links
|
|
386
386
|
footer.allRightsReserved: All rights reserved.
|
|
387
|
-
footer.madeInCzechRepublic: Made with
|
|
387
|
+
footer.madeInCzechRepublic: Made with {emoji} in Europe
|
|
388
388
|
footer.engineVersion: Promptbook engine version
|
|
389
389
|
forbidden.title: 403 Forbidden
|
|
390
390
|
forbidden.message: You do not have permission to access this page.
|
|
@@ -21,13 +21,15 @@ export function applyEmbeddingHeader(response: NextResponse, url: URL, isAllowed
|
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// Note: The base strict CSP (script-src with nonce) is preserved by appending a separate
|
|
25
|
+
// Content-Security-Policy header that only carries the `frame-ancestors` directive.
|
|
24
26
|
if (isAllowed) {
|
|
25
|
-
response.headers.
|
|
27
|
+
response.headers.append('Content-Security-Policy', 'frame-ancestors https: http:');
|
|
26
28
|
response.headers.delete('X-Frame-Options');
|
|
27
29
|
return;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
response.headers.
|
|
32
|
+
response.headers.append('Content-Security-Policy', "frame-ancestors 'none'");
|
|
31
33
|
response.headers.set('X-Frame-Options', 'DENY');
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Name of the request header that carries the per-request CSP nonce to server components.
|
|
5
|
+
*
|
|
6
|
+
* Server components read it via `next/headers` and must pass it to every inline `<script>` tag so
|
|
7
|
+
* the browser executes only scripts explicitly rendered by the server.
|
|
8
|
+
*
|
|
9
|
+
* @private function of middleware
|
|
10
|
+
*/
|
|
11
|
+
export const CONTENT_SECURITY_POLICY_NONCE_REQUEST_HEADER = 'x-csp-nonce';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Length (in bytes) of the random material used to derive each per-request CSP nonce.
|
|
15
|
+
*
|
|
16
|
+
* 16 bytes (128 bits) is the value recommended by the W3C CSP3 specification — large enough to
|
|
17
|
+
* make guessing infeasible while keeping the base64 representation short.
|
|
18
|
+
*
|
|
19
|
+
* @private function of middleware
|
|
20
|
+
*/
|
|
21
|
+
const CONTENT_SECURITY_POLICY_NONCE_BYTE_LENGTH = 16;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generates a fresh, single-use CSP nonce encoded as base64.
|
|
25
|
+
*
|
|
26
|
+
* @returns Cryptographically random nonce safe to inline into a CSP header.
|
|
27
|
+
*
|
|
28
|
+
* @private function of middleware
|
|
29
|
+
*/
|
|
30
|
+
export function generateContentSecurityPolicyNonce(): string {
|
|
31
|
+
const randomBytes = new Uint8Array(CONTENT_SECURITY_POLICY_NONCE_BYTE_LENGTH);
|
|
32
|
+
crypto.getRandomValues(randomBytes);
|
|
33
|
+
|
|
34
|
+
let binaryString = '';
|
|
35
|
+
for (const byte of randomBytes) {
|
|
36
|
+
binaryString += String.fromCharCode(byte);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Note: `btoa` is available in the Next.js Edge runtime where this middleware executes.
|
|
40
|
+
return btoa(binaryString);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Builds the strict Content Security Policy header value for the given nonce.
|
|
45
|
+
*
|
|
46
|
+
* The policy locks down `script-src` to nonced inline scripts and scripts loaded by them
|
|
47
|
+
* (`'strict-dynamic'`). The legacy fallbacks (`'unsafe-inline'`, `http:`, `https:`) are ignored
|
|
48
|
+
* by modern browsers that honor the nonce, but keep the page functional in older browsers
|
|
49
|
+
* without weakening security in modern ones.
|
|
50
|
+
*
|
|
51
|
+
* @param nonce - Per-request CSP nonce.
|
|
52
|
+
* @returns Serialized CSP header value.
|
|
53
|
+
*
|
|
54
|
+
* @private function of middleware
|
|
55
|
+
*/
|
|
56
|
+
function buildContentSecurityPolicyHeaderValue(nonce: string): string {
|
|
57
|
+
const directives = [
|
|
58
|
+
`script-src 'nonce-${nonce}' 'strict-dynamic' 'unsafe-inline' 'unsafe-eval' https: http:`,
|
|
59
|
+
`object-src 'none'`,
|
|
60
|
+
`base-uri 'self'`,
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
return directives.join('; ');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Adds the CSP nonce headers to the forwarded request so server components and the Next.js
|
|
68
|
+
* runtime can detect the nonce and apply it to their own inline scripts.
|
|
69
|
+
*
|
|
70
|
+
* @param requestHeaders - Mutable headers object that is forwarded to the route handler.
|
|
71
|
+
* @param nonce - Per-request CSP nonce.
|
|
72
|
+
*
|
|
73
|
+
* @private function of middleware
|
|
74
|
+
*/
|
|
75
|
+
export function applyContentSecurityPolicyToRequestHeaders(requestHeaders: Headers, nonce: string): void {
|
|
76
|
+
requestHeaders.set(CONTENT_SECURITY_POLICY_NONCE_REQUEST_HEADER, nonce);
|
|
77
|
+
|
|
78
|
+
// Note: Next.js detects the nonce automatically when the CSP is also present on the
|
|
79
|
+
// forwarded request headers — see https://nextjs.org/docs/app/guides/content-security-policy
|
|
80
|
+
requestHeaders.set('Content-Security-Policy', buildContentSecurityPolicyHeaderValue(nonce));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Adds the strict CSP response header so the browser enforces the nonced script policy.
|
|
85
|
+
*
|
|
86
|
+
* Helpers that add narrower directives (e.g. `applyEmbeddingHeader` with `frame-ancestors`)
|
|
87
|
+
* must `append` a separate CSP header rather than `set` so the strict `script-src` policy is
|
|
88
|
+
* preserved.
|
|
89
|
+
*
|
|
90
|
+
* @param response - Outgoing response.
|
|
91
|
+
* @param nonce - Per-request CSP nonce.
|
|
92
|
+
*
|
|
93
|
+
* @private function of middleware
|
|
94
|
+
*/
|
|
95
|
+
export function applyContentSecurityPolicyHeader(response: NextResponse, nonce: string): void {
|
|
96
|
+
response.headers.set('Content-Security-Policy', buildContentSecurityPolicyHeaderValue(nonce));
|
|
97
|
+
}
|
package/esm/index.es.js
CHANGED
|
@@ -58,7 +58,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
58
58
|
* @generated
|
|
59
59
|
* @see https://github.com/webgptorg/promptbook
|
|
60
60
|
*/
|
|
61
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
61
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-132';
|
|
62
62
|
/**
|
|
63
63
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
64
64
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
package/esm/src/version.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare const BOOK_LANGUAGE_VERSION: string_semantic_version;
|
|
|
15
15
|
export declare const PROMPTBOOK_ENGINE_VERSION: string_promptbook_version;
|
|
16
16
|
/**
|
|
17
17
|
* Represents the version string of the Promptbook engine.
|
|
18
|
-
* It follows semantic versioning (e.g., `0.112.0-
|
|
18
|
+
* It follows semantic versioning (e.g., `0.112.0-131`).
|
|
19
19
|
*
|
|
20
20
|
* @generated
|
|
21
21
|
*/
|
package/package.json
CHANGED