├── .editorconfig
├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── npm_publish.yml
│ └── tests.yml
├── .gitignore
├── .hooks
└── pre-commit
├── .moon
├── toolchain.yml
└── workspace.yml
├── .node-version
├── @kindspells
└── astro-shield
│ ├── LICENSE
│ ├── README.md
│ ├── moon.yml
│ ├── package.json
│ ├── rollup.config.mjs
│ ├── src
│ ├── core.mts
│ ├── e2e
│ │ ├── e2e.test.mts
│ │ └── fixtures
│ │ │ ├── dynamic
│ │ │ ├── astro.config.mjs
│ │ │ ├── package.json
│ │ │ └── src
│ │ │ │ ├── env.d.ts
│ │ │ │ └── pages
│ │ │ │ └── index.astro
│ │ │ ├── hybrid
│ │ │ ├── astro.config.mjs
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── code.js
│ │ │ └── src
│ │ │ │ ├── env.d.ts
│ │ │ │ └── pages
│ │ │ │ ├── index.astro
│ │ │ │ └── static.astro
│ │ │ ├── hybrid2
│ │ │ ├── astro.config.mjs
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── code.js
│ │ │ └── src
│ │ │ │ ├── env.d.ts
│ │ │ │ ├── pages
│ │ │ │ ├── index.astro
│ │ │ │ └── static.astro
│ │ │ │ └── styles
│ │ │ │ ├── main.css
│ │ │ │ └── normalize.css
│ │ │ ├── hybrid3
│ │ │ ├── astro.config.mjs
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── code.js
│ │ │ └── src
│ │ │ │ ├── env.d.ts
│ │ │ │ ├── pages
│ │ │ │ ├── index.astro
│ │ │ │ ├── injected.astro
│ │ │ │ └── static.astro
│ │ │ │ └── styles
│ │ │ │ ├── main.css
│ │ │ │ └── normalize.css
│ │ │ └── static
│ │ │ ├── astro.config.mjs
│ │ │ ├── package.json
│ │ │ └── src
│ │ │ ├── env.d.ts
│ │ │ └── pages
│ │ │ └── index.astro
│ ├── fs.mts
│ ├── headers.mts
│ ├── main.mts
│ ├── netlify.mts
│ ├── state.mts
│ ├── tests
│ │ ├── core.test.mts
│ │ ├── fixtures
│ │ │ ├── fake.css
│ │ │ ├── fake.js
│ │ │ ├── nested
│ │ │ │ └── nested.js
│ │ │ ├── netlify_headers
│ │ │ └── vercel_config.json
│ │ ├── fs.test.mts
│ │ ├── headers.test.mts
│ │ ├── main.test.mts
│ │ ├── netlify.test.mts
│ │ ├── state.test.mts
│ │ ├── utils.test.mts
│ │ └── vercel.test.mts
│ ├── types.mts
│ ├── utils.mts
│ └── vercel.mts
│ ├── tsconfig.dts.json
│ ├── tsconfig.json
│ ├── vitest.config.e2e.mts
│ └── vitest.config.unit.mts
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── biome.json
├── docs
├── .node-version
├── LICENSE
├── README.md
├── astro.config.mjs
├── moon.yml
├── package.json
├── public
│ └── favicon.svg
├── src
│ ├── assets
│ │ └── astro-shield.webp
│ ├── content
│ │ ├── config.ts
│ │ └── docs
│ │ │ ├── ca
│ │ │ ├── getting-started.mdx
│ │ │ └── index.mdx
│ │ │ ├── es
│ │ │ ├── getting-started.mdx
│ │ │ ├── guides
│ │ │ │ ├── hosting-integrations
│ │ │ │ │ ├── netlify.mdx
│ │ │ │ │ └── vercel.mdx
│ │ │ │ ├── security-headers
│ │ │ │ │ └── content-security-policy.mdx
│ │ │ │ └── subresource-integrity
│ │ │ │ │ ├── middleware.mdx
│ │ │ │ │ └── static-sites.mdx
│ │ │ ├── index.mdx
│ │ │ └── other
│ │ │ │ ├── known-limitations.md
│ │ │ │ └── team-services.md
│ │ │ ├── getting-started.mdx
│ │ │ ├── guides
│ │ │ ├── hosting-integrations
│ │ │ │ ├── netlify.mdx
│ │ │ │ └── vercel.mdx
│ │ │ ├── security-headers
│ │ │ │ └── content-security-policy.mdx
│ │ │ └── subresource-integrity
│ │ │ │ ├── middleware.mdx
│ │ │ │ └── static-sites.mdx
│ │ │ ├── hi
│ │ │ ├── getting-started.mdx
│ │ │ └── index.mdx
│ │ │ ├── index.mdx
│ │ │ ├── other
│ │ │ ├── known-limitations.md
│ │ │ └── team-services.md
│ │ │ ├── reference
│ │ │ └── configuration.mdx
│ │ │ └── ru
│ │ │ ├── getting-started.mdx
│ │ │ ├── guides
│ │ │ ├── hosting-integrations
│ │ │ │ ├── netlify.mdx
│ │ │ │ └── vercel.mdx
│ │ │ ├── security-headers
│ │ │ │ └── content-security-policy.mdx
│ │ │ └── subresource-integrity
│ │ │ │ ├── middleware.mdx
│ │ │ │ └── static-sites.mdx
│ │ │ ├── index.mdx
│ │ │ ├── other
│ │ │ ├── known-limitations.mdx
│ │ │ └── team-services.mdx
│ │ │ └── reference
│ │ │ └── configuration.mdx
│ ├── env.d.ts
│ └── sst-env.d.ts
├── sst-env.d.ts
├── sst.config.ts
└── tsconfig.json
├── package.json
├── pnpm-lock.yaml
└── pnpm-workspace.yaml
/.editorconfig:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
11 | [*.{js,mjs,mts,json,ts,astro},.hooks/*]
12 | charset = utf-8
13 | indent_style = tab
14 | indent_size = 2
15 | trim_trailing_whitespace = true
16 |
17 | [*.{md,mdx}]
18 | charset = utf-8
19 | indent_style = space
20 | indent_size = 2
21 | trim_trailing_whitespace = true
22 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | github: [KindSpells, castarco]
6 | open_collective: kindspells-labs
7 | polar: kindspells
8 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "npm" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 | groups:
13 | prod-deps-security:
14 | applies-to: "security-updates"
15 | dependency-type: "production"
16 | dev-deps-security:
17 | applies-to: "security-updates"
18 | dependency-type: "development"
19 | prod-deps:
20 | applies-to: "version-updates"
21 | dependency-type: "production"
22 | dev-deps:
23 | applies-to: "version-updates"
24 | dependency-type: "development"
25 | ignore:
26 | - dependency-name: "@types/node"
27 | update-types:
28 | - "version-update:semver-major"
29 | - "version-update:semver-minor"
30 | - "version-update:semver-patch"
31 | - dependency-name: "typescript"
32 | update-types:
33 | - "version-update:semver-major"
34 | - "version-update:semver-minor"
35 | - "version-update:semver-patch"
36 |
--------------------------------------------------------------------------------
/.github/workflows/npm_publish.yml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | name: Publish Package to npmjs
6 | on:
7 | release:
8 | types: [created]
9 | jobs:
10 | build:
11 | strategy:
12 | matrix:
13 | node-version: [22]
14 | os: [ubuntu-24.04]
15 |
16 | runs-on: ${{ matrix.os }}
17 |
18 | permissions:
19 | contents: read
20 | id-token: write
21 |
22 | steps:
23 | - name: Checkout repository # v4.1.1
24 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
25 | - name: Install PNPM # v3.0.0
26 | uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d
27 | with:
28 | version: '9.12.0'
29 | - name: Use Node.js ${{ matrix.node-version }} # v4.0.2
30 | uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8
31 | with:
32 | node-version: ${{ matrix.node-version }}
33 | cache: 'pnpm'
34 | registry-url: 'https://registry.npmjs.org'
35 | - name: Install dependencies
36 | run: pnpm install --frozen-lockfile
37 | - name: Publish to NPM registry
38 | run: pnpm publish --provenance --no-git-checks --access public
39 | working-directory: ./@kindspells/astro-shield
40 | env:
41 | NPM_CONFIG_PROVENANCE: 'true'
42 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
43 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | name: Tests
6 |
7 | on:
8 | push:
9 | branches: [ main ]
10 | pull_request:
11 | branches: [ main ]
12 | merge_group:
13 | branches: [ main]
14 |
15 | defaults:
16 | run:
17 | working-directory: .
18 |
19 | jobs:
20 | build:
21 | strategy:
22 | matrix:
23 | node-version: [ 18, 20, 22 ]
24 | os: [ubuntu-24.04]
25 |
26 | runs-on: ${{ matrix.os }}
27 |
28 | steps:
29 | - name: Checkout repository # v4.1.1
30 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
31 | with:
32 | fetch-depth: 0
33 | - name: Install PNPM # v3.0.0
34 | uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d
35 | with:
36 | version: '9.12.0'
37 | - name: Use Node.js ${{ matrix.node-version }} # v4.0.2
38 | uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8
39 | with:
40 | node-version: ${{ matrix.node-version }}
41 | cache: 'pnpm'
42 |
43 | - name: Install dependencies
44 | run: pnpm install --frozen-lockfile
45 | - name: Run Moon CI checks
46 | run: pnpm moon ci
47 | env:
48 | MOONBASE_SECRET_KEY: ${{ secrets.MOONBASE_SECRET_KEY }}
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | # coverage
6 | **/coverage/
7 | **/coverage-e2e/
8 | **/coverage-unit/
9 |
10 | # vendored dependencies
11 | **/node_modules/
12 |
13 | # build & cache artifacts
14 | **/.astro/
15 | **/.sst/
16 | **/dist/
17 | **/generated/
18 |
19 | # testing artifacts
20 | **/tests/playground/*.mjs
21 | **/*-test-artifact.*
22 |
23 | # moon
24 | .moon/cache
25 | .moon/docker
26 |
27 | # logs
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | pnpm-debug.log*
32 |
33 | # macOS-specific files
34 | .DS_Store
35 |
--------------------------------------------------------------------------------
/.hooks/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | set -eu
8 | set -o pipefail
9 |
10 | pnpm moon ci
11 | pnpm moon :test.e2e
12 |
--------------------------------------------------------------------------------
/.moon/toolchain.yml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | node:
6 | packageManager: 'pnpm'
7 | pnpm:
8 | version: 9.12.0
9 |
--------------------------------------------------------------------------------
/.moon/workspace.yml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | # https://moonrepo.dev/docs/config/workspace
6 | $schema: 'https://moonrepo.dev/schemas/workspace.json'
7 |
8 | projects:
9 | globs:
10 | - '@kindspells/astro-shield/e2e/fixtures/*'
11 | sources:
12 | astro-shield: '@kindspells/astro-shield'
13 | docs: 'docs'
14 |
15 | vcs:
16 | defaultBranch: 'main'
17 |
--------------------------------------------------------------------------------
/.node-version:
--------------------------------------------------------------------------------
1 | 22.9.0
2 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 KindSpells Labs S.L.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/README.md:
--------------------------------------------------------------------------------
1 |
6 | # Astro-Shield
7 |
8 | [](https://www.npmjs.com/package/@kindspells/astro-shield)
9 | 
10 | 
11 | 
12 | [](https://socket.dev/npm/package/@kindspells/astro-shield)
13 |
14 | ## Introduction
15 |
16 | Astro-Shield helps you to enhance the security of your Astro site.
17 |
18 | ## How to install
19 |
20 | ```bash
21 | # With NPM
22 | npm install --save-dev @kindspells/astro-shield
23 |
24 | # With Yarn
25 | yarn add --dev @kindspells/astro-shield
26 |
27 | # With PNPM
28 | pnpm add --save-dev @kindspells/astro-shield
29 | ```
30 |
31 | ## How to use
32 |
33 | In your `astro.config.mjs` file:
34 |
35 | ```javascript
36 | import { defineConfig } from 'astro/config'
37 | import { shield } from '@kindspells/astro-shield'
38 |
39 | export default defineConfig({
40 | integrations: [
41 | shield({})
42 | ]
43 | })
44 | ```
45 |
46 | ## Learn more
47 |
48 | - [Astro-Shield Documentation](https://astro-shield.kindspells.dev)
49 |
50 | ## Other Relevant Guidelines
51 |
52 | - [Code of Conduct](https://github.com/KindSpells/astro-shield?tab=coc-ov-file)
53 | - [Contributing Guidelines](https://github.com/KindSpells/astro-shield/blob/main/CONTRIBUTING.md)
54 | - [Security Policy](https://github.com/KindSpells/astro-shield/security/policy)
55 |
56 | ## Main Contributors
57 |
58 | This library has been created and is being maintained by
59 | [KindSpells Labs](https://kindspells.dev/?utm_source=github&utm_medium=astro_sri_scp&utm_campaign=floss).
60 |
61 | ## License
62 |
63 | This library is released under [MIT License](https://github.com/KindSpells/astro-shield?tab=MIT-1-ov-file).
64 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/moon.yml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | type: 'library'
6 | platform: 'node'
7 |
8 | tasks:
9 | build:
10 | platform: 'system'
11 | script: 'rm -rf ./dist && pnpm rollup --config ./rollup.config.mjs'
12 | inputs:
13 | - 'rollup.config.mjs'
14 | - 'package.json'
15 | - 'src/**/*'
16 | outputs:
17 | - 'dist/**/*'
18 | lint:
19 | deps:
20 | - '~:lint.biome'
21 | - '~:lint.publint'
22 | - '~:lint.tsc'
23 | lint.biome:
24 | command: 'biome lint . --error-on-warnings'
25 | inputs:
26 | - '*.json'
27 | - '*.mts'
28 | - 'src/**/*'
29 | - 'tests/**/*'
30 | - 'e2e/**/*'
31 | lint.publint:
32 | command: 'publint'
33 | inputs:
34 | - 'package.json'
35 | - 'src/**/*'
36 | deps:
37 | - '~:build'
38 | lint.tsc:
39 | command: 'tsc -p . --noEmit'
40 | inputs:
41 | - 'package.json'
42 | - 'tsconfig.json'
43 | - 'src/**/*'
44 | - 'tests/**/*.mts'
45 | - 'e2e/**/*.mts'
46 | test.e2e:
47 | command: 'vitest -c vitest.config.e2e.mts run'
48 | inputs:
49 | - 'vitest.config.e2e.mts'
50 | - 'src/**/*'
51 | - 'e2e/**/*'
52 | options:
53 | runInCI: false
54 | deps:
55 | - '~:build'
56 | test.unit:
57 | command: 'vitest -c vitest.config.unit.mts run'
58 | inputs:
59 | - 'vitest.config.unit.mts'
60 | - 'src/**/*'
61 | - 'tests/**/*'
62 | options:
63 | runInCI: false
64 | deps:
65 | - '~:build'
66 | test.unit.cov:
67 | command: 'vitest -c vitest.config.unit.mts run --coverage'
68 | inputs:
69 | - 'vitest.config.unit.mts'
70 | - 'src/**/*'
71 | - 'tests/**/*'
72 | outputs:
73 | - 'coverage-unit/**/*'
74 | deps:
75 | - '~:build'
76 | test:
77 | deps:
78 | - '~:test.unit.cov'
79 | - '~:test.e2e'
80 | options:
81 | runInCI: false
82 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@kindspells/astro-shield",
3 | "version": "1.7.1",
4 | "description": "Astro integration to enhance your website's security with SubResource Integrity hashes, Content-Security-Policy headers, and other techniques.",
5 | "private": false,
6 | "type": "module",
7 | "main": "dist/main.mjs",
8 | "types": "dist/main.d.mts",
9 | "exports": {
10 | ".": {
11 | "types": "./dist/main.d.mts",
12 | "import": "./dist/main.mjs",
13 | "default": "./dist/main.mjs"
14 | },
15 | "./core": {
16 | "import": "./dist/core.mjs",
17 | "default": "./dist/core.mjs"
18 | },
19 | "./state": {
20 | "import": "./dist/state.mjs",
21 | "default": "./dist/state.mjs"
22 | }
23 | },
24 | "imports": {
25 | "#as/*": {
26 | "types": "./src/*.mts",
27 | "import": "./dist/*.mjs"
28 | }
29 | },
30 | "files": ["dist/*"],
31 | "scripts": {
32 | "build": "moon run build",
33 | "format": "biome format --write .",
34 | "lint": "moon run lint",
35 | "lint:biome": "moon run lint.biome",
36 | "lint:publint": "moon run lint.publint",
37 | "lint:tsc": "moon run lint.tsc",
38 | "prepublishOnly": "pnpm lint && pnpm test:unit:cov",
39 | "test:e2e": "moon run test.e2e",
40 | "test:unit": "moon run test.unit",
41 | "test:unit:cov": "moon run test.unit.cov"
42 | },
43 | "keywords": [
44 | "astro",
45 | "astro-component",
46 | "astro-integration",
47 | "code-generation",
48 | "csp",
49 | "content-security-policy",
50 | "security",
51 | "sri",
52 | "subresource-integrity",
53 | "withastro"
54 | ],
55 | "contributors": [
56 | {
57 | "name": "Andres Correa Casablanca",
58 | "url": "https://blog.coderspirit.xyz/about/"
59 | }
60 | ],
61 | "license": "MIT",
62 | "peerDependencies": {
63 | "astro": "^4.0.0"
64 | },
65 | "devDependencies": {
66 | "@types/node": "^22.8.6",
67 | "astro": "^4.16.8",
68 | "get-tsconfig": "^4.8.1",
69 | "rollup": "^4.24.3",
70 | "rollup-plugin-dts": "^6.1.1",
71 | "rollup-plugin-esbuild": "^6.1.1",
72 | "typescript": "^5.6.3",
73 | "vite": "^5.4.10",
74 | "vitest": "^2.1.4"
75 | },
76 | "repository": {
77 | "type": "git",
78 | "url": "git+https://github.com/kindspells/astro-shield.git"
79 | },
80 | "homepage": "https://astro-shield.kindspells.dev",
81 | "bugs": "https://github.com/kindspells/astro-shield/issues",
82 | "funding": [
83 | {
84 | "type": "opencollective",
85 | "url": "https://opencollective.com/kindspells-labs"
86 | },
87 | {
88 | "type": "individual",
89 | "url": "https://ko-fi.com/coderspirit"
90 | }
91 | ],
92 | "packageManager": "pnpm@9.11.0",
93 | "engines": {
94 | "node": ">= 18.0.0"
95 | },
96 | "publishConfig": {
97 | "provenance": true
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import { dirname, resolve } from 'node:path'
2 | import { fileURLToPath } from 'node:url'
3 |
4 | import { getTsconfig } from 'get-tsconfig'
5 | import { defineConfig } from 'rollup'
6 | import { dts } from 'rollup-plugin-dts'
7 | import esbuild from 'rollup-plugin-esbuild'
8 |
9 | const projectDir = dirname(fileURLToPath(import.meta.url))
10 | const tsconfig = getTsconfig(projectDir)
11 | const target = tsconfig?.config.compilerOptions?.target ?? 'es2022'
12 |
13 | const baseConfig = {
14 | plugins: [
15 | esbuild({
16 | target: ['node18', 'node20', 'node22', target],
17 | loaders: { '.mts': 'ts' },
18 | keepNames: true,
19 | minifyIdentifiers: true,
20 | minifySyntax: true,
21 | minifyWhitespace: false,
22 | treeShaking: true,
23 | }),
24 | ],
25 | external: ['node:crypto', 'node:fs/promises', 'node:path', 'node:url'],
26 | }
27 |
28 | const outputConfig = /** @type {import('rollup').OutputOptions} */ ({
29 | format: 'esm',
30 | indent: '\t', // With any luck, some day esbuild will support this option
31 | sourcemap: true,
32 | })
33 |
34 | export default defineConfig([
35 | {
36 | input: 'src/core.mts',
37 | output: [{ ...outputConfig, file: 'dist/core.mjs' }],
38 | ...baseConfig,
39 | },
40 | {
41 | input: 'src/main.mts',
42 | output: [{ ...outputConfig, file: 'dist/main.mjs' }],
43 | external: ['#as/core'],
44 | ...baseConfig,
45 | },
46 | {
47 | input: 'src/state.mts',
48 | output: [{ ...outputConfig, file: 'dist/state.mjs' }],
49 | ...baseConfig,
50 | },
51 | {
52 | input: 'src/main.mts',
53 | output: [{ format: 'esm', file: 'dist/main.d.mts' }],
54 | external: ['#as/core'],
55 | // TODO: When possible, pass `noCheck: true` instead of loading an entire tsconfig file
56 | plugins: [dts({ tsconfig: resolve(projectDir, 'tsconfig.dts.json') })],
57 | },
58 | ])
59 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/dynamic/astro.config.mjs:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import node from '@astrojs/node'
8 | import { shield } from '@kindspells/astro-shield'
9 | import { defineConfig } from 'astro/config'
10 |
11 | // https://astro.build/config
12 | export default defineConfig({
13 | output: 'server',
14 | trailingSlash: 'always',
15 | adapter: node({ mode: 'standalone' }),
16 | integrations: [
17 | shield({
18 | sri: {
19 | enableStatic: false,
20 | enableMiddleware: true,
21 | },
22 | }),
23 | ],
24 | })
25 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/dynamic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dynamic",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "type": "module",
7 | "scripts": {
8 | "build": "astro build",
9 | "clean": "rm -rf ./dist; rm -rf ./src/generated/*"
10 | },
11 | "license": "MIT",
12 | "dependencies": {
13 | "@astrojs/node": "^8.3.4",
14 | "astro": "^4.16.8"
15 | },
16 | "devDependencies": {
17 | "@kindspells/astro-shield": "workspace:*"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/dynamic/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/dynamic/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /*
3 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
4 | *
5 | * SPDX-License-Identifier: MIT
6 | */
7 |
8 | const myConst = 42
9 | ---
10 |
11 |
12 |
13 |
14 | My Static Test Site
15 |
16 |
17 |
18 |
19 | My const is: { myConst }
20 |
21 |
22 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid/astro.config.mjs:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import { resolve } from 'node:path'
8 | import node from '@astrojs/node'
9 | import { shield } from '@kindspells/astro-shield'
10 | import { defineConfig } from 'astro/config'
11 |
12 | const rootDir = new URL('.', import.meta.url).pathname
13 | const hashesModule = resolve(rootDir, 'src', 'generated', 'sri.mjs')
14 |
15 | // https://astro.build/config
16 | export default defineConfig({
17 | output: 'hybrid',
18 | trailingSlash: 'always',
19 | adapter: node({ mode: 'standalone' }),
20 | integrations: [
21 | shield({
22 | sri: {
23 | enableStatic: true,
24 | enableMiddleware: true,
25 | hashesModule,
26 | },
27 | }),
28 | ],
29 | })
30 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hybrid",
3 | "version": "1.0.0",
4 | "type": "module",
5 | "scripts": {
6 | "build": "astro build",
7 | "clean": "rm -rf ./dist; rm -rf ./src/generated/*"
8 | },
9 | "license": "MIT",
10 | "dependencies": {
11 | "@astrojs/node": "^8.3.4",
12 | "astro": "^4.16.8"
13 | },
14 | "devDependencies": {
15 | "@kindspells/astro-shield": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid/public/code.js:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | alert('Hello!')
8 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /*
3 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
4 | *
5 | * SPDX-License-Identifier: MIT
6 | */
7 |
8 | const myConst = 42
9 | export const prerender = false
10 | ---
11 |
12 |
13 |
14 |
15 | My Static Test Site
16 |
17 |
18 |
19 |
20 | My const is: { myConst }
21 |
22 |
23 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid/src/pages/static.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /*
3 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
4 | *
5 | * SPDX-License-Identifier: MIT
6 | */
7 |
8 | const myConst = 42
9 | export const prerender = true
10 | ---
11 |
12 |
13 |
14 |
15 | My Static Test Site
16 |
17 |
18 |
19 |
20 | My const is: { myConst }
21 |
22 |
23 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid2/astro.config.mjs:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import { resolve } from 'node:path'
8 | import node from '@astrojs/node'
9 | import { shield } from '@kindspells/astro-shield'
10 | import { defineConfig } from 'astro/config'
11 |
12 | const rootDir = new URL('.', import.meta.url).pathname
13 | const hashesModule = resolve(rootDir, 'src', 'generated', 'sri.mjs')
14 |
15 | // https://astro.build/config
16 | export default defineConfig({
17 | output: 'hybrid',
18 | trailingSlash: 'always',
19 | adapter: node({ mode: 'standalone' }),
20 | integrations: [
21 | shield({
22 | sri: {
23 | enableStatic: true,
24 | enableMiddleware: true,
25 | hashesModule,
26 | },
27 | }),
28 | ],
29 | vite: {
30 | build: { assetsInlineLimit: 1024 },
31 | },
32 | })
33 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hybrid2",
3 | "version": "1.0.0",
4 | "type": "module",
5 | "scripts": {
6 | "build": "astro build",
7 | "clean": "rm -rf ./dist; rm -rf ./src/generated/*"
8 | },
9 | "license": "MIT",
10 | "dependencies": {
11 | "@astrojs/node": "^8.3.4",
12 | "astro": "^4.16.8"
13 | },
14 | "devDependencies": {
15 | "@kindspells/astro-shield": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid2/public/code.js:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | alert('Hello!')
8 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid2/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid2/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /*
3 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
4 | *
5 | * SPDX-License-Identifier: MIT
6 | */
7 |
8 | const myConst = 42
9 | export const prerender = false
10 | import '../styles/main.css'
11 | ---
12 |
13 |
14 |
15 |
16 | My Static Test Site
17 |
18 |
19 |
20 |
21 | My const is: { myConst }
22 | In this document we'll try to load all our CSS rules
23 |
24 |
25 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid2/src/pages/static.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /*
3 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
4 | *
5 | * SPDX-License-Identifier: MIT
6 | */
7 |
8 | const myConst = 42
9 | export const prerender = true
10 | import '../styles/main.css'
11 | ---
12 |
13 |
14 |
15 |
16 | My Static Test Site
17 |
18 |
19 |
20 |
21 | My const is: { myConst }
22 | In this document we'll try to load all our CSS rules
23 |
24 |
25 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid2/src/styles/main.css:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | @import './normalize.css';
8 |
9 | /*
10 | * Global
11 | *********************************************************************/
12 | :where(html) {
13 | --ease-3: cubic-bezier(.25, 0, .3, 1);
14 | --ease-elastic-3: var(--ease-elastic-out-3);
15 | --ease-elastic-4: var(--ease-elastic-out-4);
16 | --ease-out-5: cubic-bezier(0, 0, 0, 1);
17 | }
18 |
19 | :root {
20 | color-scheme: light;
21 | --bg-color: rgb(250, 250, 250);
22 | --bg-color-2: rgb(34, 39, 42);
23 | --text-color: rgb(34, 39, 42);
24 | --text-color-2: rgb(167, 167, 168);
25 | --accent-color: rgb(203, 42, 66);
26 | --accent-color-2: rgb(41, 188, 137);
27 |
28 | --footer-bg-color: var(--bg-color-2);
29 | --footer-links: var(--text-color);
30 | }
31 |
32 | :root.dark {
33 | color-scheme: dark;
34 | --bg-color: rgb(34, 39, 42);
35 | --bg-color-2: rgb(230, 230, 230);
36 | --text-color: rgb(230, 230, 230);
37 | --text-color-2: rgb(34, 39, 42);
38 | --accent-color: rgb(41, 188, 137);
39 | --accent-color-2: rgb(249, 180, 19);
40 |
41 | --footer-bg-color: var(--bg-color-2);
42 | --footer-links: var(--text-color);
43 | }
44 |
45 | html {
46 | height: 100%;
47 | }
48 |
49 | body {
50 | color: var(--text-color);
51 | background-color: var(--bg-color);
52 | display: flex;
53 | flex-flow: column;
54 | height: 100%;
55 | }
56 |
57 | /*
58 | * Main Content
59 | *********************************************************************/
60 | main {
61 | max-width: 720px;
62 | min-width: 260px;
63 | width: calc(75% + 48px);
64 | margin: 0 auto;
65 | padding: 0;
66 | display: flex;
67 | flex: 1;
68 | }
69 |
70 | #splash {
71 | display: flex;
72 | flex: 1;
73 | align-items: center;
74 | justify-content: center;
75 | }
76 |
77 | #hero p {
78 | text-align: center;
79 | margin: 16px auto 24px auto;
80 | }
81 |
82 | h1 {
83 | font-size: 2.5rem;
84 | font-weight: 800;
85 | }
86 |
87 | h2 {
88 | font-size: 1.5rem;
89 | font-weight: 700;
90 | }
91 |
92 | #hero h1 {
93 | text-align: center
94 | }
95 |
96 | #document-block {
97 | margin-bottom: 32px;
98 | }
99 |
100 | #document-block p, #document-block ul {
101 | margin-bottom: 12px;
102 | }
103 |
104 | #document-block ul li {
105 | list-style: inside square;
106 | }
107 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid2/src/styles/normalize.css:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 Sindre Sorhus, Jonathan Neal, Nicolas Gallagher
3 | * SPDX-License-Identifier: MIT
4 | */
5 |
6 | /*
7 | Document
8 | ========
9 | */
10 |
11 | /**
12 | Use a better box model (opinionated).
13 | */
14 |
15 | *,
16 | ::before,
17 | ::after {
18 | box-sizing: border-box;
19 | }
20 |
21 | html {
22 | /* Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) */
23 | font-family:
24 | system-ui,
25 | 'Segoe UI',
26 | Roboto,
27 | Helvetica,
28 | Arial,
29 | sans-serif,
30 | 'Apple Color Emoji',
31 | 'Segoe UI Emoji';
32 | line-height: 1.15; /* 1. Correct the line height in all browsers. */
33 | -webkit-text-size-adjust: 100%; /* 2. Prevent adjustments of font size after orientation changes in iOS. */
34 | text-size-adjust: 100%; /* same as above, but in its "final form" */
35 | -moz-tab-size: 4; /* 3. Use a more readable tab size (opinionated). */
36 | tab-size: 4; /* 3 */
37 | }
38 |
39 | /*
40 | Sections
41 | ========
42 | */
43 |
44 | body {
45 | margin: 0; /* Remove the margin in all browsers. */
46 | }
47 |
48 | /*
49 | Grouping content
50 | ================
51 | */
52 |
53 | /**
54 | 1. Add the correct height in Firefox.
55 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
56 | */
57 |
58 | hr {
59 | height: 0; /* 1 */
60 | color: inherit; /* 2 */
61 | }
62 |
63 | /*
64 | Text-level semantics
65 | ====================
66 | */
67 |
68 | /**
69 | Add the correct text decoration in Chrome, Edge, and Safari.
70 | */
71 |
72 | abbr[title] {
73 | text-decoration: underline dotted;
74 | }
75 |
76 | /**
77 | Add the correct font weight in Edge and Safari.
78 | */
79 |
80 | b,
81 | strong {
82 | font-weight: bolder;
83 | }
84 |
85 | /**
86 | 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
87 | 2. Correct the odd 'em' font sizing in all browsers.
88 | */
89 |
90 | code,
91 | kbd,
92 | samp,
93 | pre {
94 | font-family:
95 | ui-monospace,
96 | SFMono-Regular,
97 | Consolas,
98 | 'Liberation Mono',
99 | Menlo,
100 | monospace; /* 1 */
101 | font-size: 1em; /* 2 */
102 | }
103 |
104 | /**
105 | Add the correct font size in all browsers.
106 | */
107 |
108 | small {
109 | font-size: 80%;
110 | }
111 |
112 | /**
113 | Prevent 'sub' and 'sup' elements from affecting the line height in all browsers.
114 | */
115 |
116 | sub,
117 | sup {
118 | font-size: 75%;
119 | line-height: 0;
120 | position: relative;
121 | vertical-align: baseline;
122 | }
123 |
124 | sub {
125 | bottom: -0.25em;
126 | }
127 |
128 | sup {
129 | top: -0.5em;
130 | }
131 |
132 | /*
133 | Tabular data
134 | ============
135 | */
136 |
137 | /**
138 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
139 | 2. Correct table border color inheritance in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
140 | */
141 |
142 | table {
143 | text-indent: 0; /* 1 */
144 | border-color: inherit; /* 2 */
145 | }
146 |
147 | /*
148 | Forms
149 | =====
150 | */
151 |
152 | /**
153 | 1. Change the font styles in all browsers.
154 | 2. Remove the margin in Firefox and Safari.
155 | */
156 |
157 | button,
158 | input,
159 | optgroup,
160 | select,
161 | textarea {
162 | font-family: inherit; /* 1 */
163 | font-size: 100%; /* 1 */
164 | line-height: 1.15; /* 1 */
165 | margin: 0; /* 2 */
166 | }
167 |
168 | /**
169 | Remove the inheritance of text transform in Edge and Firefox.
170 | */
171 |
172 | button,
173 | select {
174 | text-transform: none;
175 | }
176 |
177 | /**
178 | Correct the inability to style clickable types in iOS and Safari.
179 | */
180 |
181 | button,
182 | [type='button'],
183 | [type='reset'],
184 | [type='submit'] {
185 | -webkit-appearance: button;
186 | }
187 |
188 | /**
189 | Remove the inner border and padding in Firefox.
190 | */
191 |
192 | ::-moz-focus-inner {
193 | border-style: none;
194 | padding: 0;
195 | }
196 |
197 | /**
198 | Restore the focus styles unset by the previous rule.
199 | */
200 |
201 | :-moz-focusring {
202 | outline: 1px dotted ButtonText;
203 | }
204 |
205 | /**
206 | Remove the additional ':invalid' styles in Firefox.
207 | See: https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737
208 | */
209 |
210 | :-moz-ui-invalid {
211 | box-shadow: none;
212 | }
213 |
214 | /**
215 | Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers.
216 | */
217 |
218 | legend {
219 | padding: 0;
220 | }
221 |
222 | /**
223 | Add the correct vertical alignment in Chrome and Firefox.
224 | */
225 |
226 | progress {
227 | vertical-align: baseline;
228 | }
229 |
230 | /**
231 | Correct the cursor style of increment and decrement buttons in Safari.
232 | */
233 |
234 | ::-webkit-inner-spin-button,
235 | ::-webkit-outer-spin-button {
236 | height: auto;
237 | }
238 |
239 | /**
240 | 1. Correct the odd appearance in Chrome and Safari.
241 | 2. Correct the outline style in Safari.
242 | */
243 |
244 | [type='search'] {
245 | -webkit-appearance: textfield; /* 1 */
246 | outline-offset: -2px; /* 2 */
247 | }
248 |
249 | /**
250 | Remove the inner padding in Chrome and Safari on macOS.
251 | */
252 |
253 | ::-webkit-search-decoration {
254 | -webkit-appearance: none;
255 | }
256 |
257 | /**
258 | 1. Correct the inability to style clickable types in iOS and Safari.
259 | 2. Change font properties to 'inherit' in Safari.
260 | */
261 |
262 | ::-webkit-file-upload-button {
263 | -webkit-appearance: button; /* 1 */
264 | font: inherit; /* 2 */
265 | }
266 |
267 | /*
268 | Interactive
269 | ===========
270 | */
271 |
272 | /*
273 | Add the correct display in Chrome and Safari.
274 | */
275 |
276 | summary {
277 | display: list-item;
278 | }
279 |
280 |
281 | /*
282 | Extra Recommendations from https://mattbrictson.com/blog/css-normalize-and-reset
283 | ================================================================================
284 | */
285 | :root { line-height: 1.5; }
286 | h1, h2, h3, h4, h5, figure, p, ol, ul {
287 | margin: 0;
288 | }
289 | ol, ul {
290 | list-style: none;
291 | padding-inline: 0;
292 | }
293 | h1, h2, h3, h4, h5 {
294 | font-size: inherit;
295 | font-weight: inherit;
296 | }
297 | img {
298 | display: block;
299 | max-inline-size: 100%;
300 | }
301 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid3/astro.config.mjs:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import { resolve } from 'node:path'
8 | import node from '@astrojs/node'
9 | import { shield } from '@kindspells/astro-shield'
10 | import { defineConfig } from 'astro/config'
11 |
12 | const rootDir = new URL('.', import.meta.url).pathname
13 | const sriHashesModule = resolve(rootDir, 'src', 'generated', 'sri.mjs')
14 |
15 | // https://astro.build/config
16 | export default defineConfig({
17 | output: 'hybrid',
18 | trailingSlash: 'always',
19 | adapter: node({ mode: 'standalone' }),
20 | integrations: [
21 | shield({
22 | sri: {
23 | enableStatic: true,
24 | enableMiddleware: true,
25 | hashesModule: sriHashesModule,
26 | scriptsAllowListUrls: [
27 | 'https://code.jquery.com/jquery-3.7.1.slim.min.js',
28 | 'https://code.jquery.com/ui/1.13.2/jquery-ui.min.js',
29 | ],
30 | },
31 | securityHeaders: {
32 | contentSecurityPolicy: {
33 | cspDirectives: {
34 | 'default-src': "'none'",
35 | 'frame-ancestors': "'none'",
36 | },
37 | },
38 | },
39 | }),
40 | ],
41 | vite: {
42 | build: { assetsInlineLimit: 1024 },
43 | },
44 | })
45 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hybrid3",
3 | "version": "1.0.0",
4 | "type": "module",
5 | "scripts": {
6 | "build": "astro build",
7 | "clean": "rm -rf ./dist; rm -rf ./src/generated/*"
8 | },
9 | "license": "MIT",
10 | "dependencies": {
11 | "@astrojs/node": "^8.3.4",
12 | "astro": "^4.16.8"
13 | },
14 | "devDependencies": {
15 | "@kindspells/astro-shield": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid3/public/code.js:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | alert('Hello!')
8 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid3/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid3/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /*
3 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
4 | *
5 | * SPDX-License-Identifier: MIT
6 | */
7 |
8 | const myConst = 42
9 | export const prerender = false
10 | import '../styles/main.css'
11 | ---
12 |
13 |
14 |
15 |
16 | My Static Test Site
17 |
18 |
19 |
20 |
21 | My const is: { myConst }
22 | In this document we'll try to load all our CSS rules
23 |
24 |
25 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid3/src/pages/injected.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /*
3 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
4 | *
5 | * SPDX-License-Identifier: MIT
6 | */
7 |
8 | export const prerender = false
9 | import '../styles/main.css'
10 | ---
11 |
12 |
13 |
14 |
15 | My Static Test Site
16 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
30 | This document simulates a page showing an injected script
31 |
32 |
33 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid3/src/pages/static.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /*
3 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
4 | *
5 | * SPDX-License-Identifier: MIT
6 | */
7 |
8 | const myConst = 42
9 | export const prerender = true
10 | import '../styles/main.css'
11 | ---
12 |
13 |
14 |
15 |
16 | My Static Test Site
17 |
18 |
19 |
20 |
21 | My const is: { myConst }
22 | In this document we'll try to load all our CSS rules
23 |
24 |
25 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid3/src/styles/main.css:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | @import './normalize.css';
8 |
9 | /*
10 | * Global
11 | *********************************************************************/
12 | :where(html) {
13 | --ease-3: cubic-bezier(.25, 0, .3, 1);
14 | --ease-elastic-3: var(--ease-elastic-out-3);
15 | --ease-elastic-4: var(--ease-elastic-out-4);
16 | --ease-out-5: cubic-bezier(0, 0, 0, 1);
17 | }
18 |
19 | :root {
20 | color-scheme: light;
21 | --bg-color: rgb(250, 250, 250);
22 | --bg-color-2: rgb(34, 39, 42);
23 | --text-color: rgb(34, 39, 42);
24 | --text-color-2: rgb(167, 167, 168);
25 | --accent-color: rgb(203, 42, 66);
26 | --accent-color-2: rgb(41, 188, 137);
27 |
28 | --footer-bg-color: var(--bg-color-2);
29 | --footer-links: var(--text-color);
30 | }
31 |
32 | :root.dark {
33 | color-scheme: dark;
34 | --bg-color: rgb(34, 39, 42);
35 | --bg-color-2: rgb(230, 230, 230);
36 | --text-color: rgb(230, 230, 230);
37 | --text-color-2: rgb(34, 39, 42);
38 | --accent-color: rgb(41, 188, 137);
39 | --accent-color-2: rgb(249, 180, 19);
40 |
41 | --footer-bg-color: var(--bg-color-2);
42 | --footer-links: var(--text-color);
43 | }
44 |
45 | html {
46 | height: 100%;
47 | }
48 |
49 | body {
50 | color: var(--text-color);
51 | background-color: var(--bg-color);
52 | display: flex;
53 | flex-flow: column;
54 | height: 100%;
55 | }
56 |
57 | /*
58 | * Main Content
59 | *********************************************************************/
60 | main {
61 | max-width: 720px;
62 | min-width: 260px;
63 | width: calc(75% + 48px);
64 | margin: 0 auto;
65 | padding: 0;
66 | display: flex;
67 | flex: 1;
68 | }
69 |
70 | #splash {
71 | display: flex;
72 | flex: 1;
73 | align-items: center;
74 | justify-content: center;
75 | }
76 |
77 | #hero p {
78 | text-align: center;
79 | margin: 16px auto 24px auto;
80 | }
81 |
82 | h1 {
83 | font-size: 2.5rem;
84 | font-weight: 800;
85 | }
86 |
87 | h2 {
88 | font-size: 1.5rem;
89 | font-weight: 700;
90 | }
91 |
92 | #hero h1 {
93 | text-align: center
94 | }
95 |
96 | #document-block {
97 | margin-bottom: 32px;
98 | }
99 |
100 | #document-block p, #document-block ul {
101 | margin-bottom: 12px;
102 | }
103 |
104 | #document-block ul li {
105 | list-style: inside square;
106 | }
107 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/hybrid3/src/styles/normalize.css:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 Sindre Sorhus, Jonathan Neal, Nicolas Gallagher
3 | * SPDX-License-Identifier: MIT
4 | */
5 |
6 | /*
7 | Document
8 | ========
9 | */
10 |
11 | /**
12 | Use a better box model (opinionated).
13 | */
14 |
15 | *,
16 | ::before,
17 | ::after {
18 | box-sizing: border-box;
19 | }
20 |
21 | html {
22 | /* Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) */
23 | font-family:
24 | system-ui,
25 | 'Segoe UI',
26 | Roboto,
27 | Helvetica,
28 | Arial,
29 | sans-serif,
30 | 'Apple Color Emoji',
31 | 'Segoe UI Emoji';
32 | line-height: 1.15; /* 1. Correct the line height in all browsers. */
33 | -webkit-text-size-adjust: 100%; /* 2. Prevent adjustments of font size after orientation changes in iOS. */
34 | text-size-adjust: 100%; /* same as above, but in its "final form" */
35 | -moz-tab-size: 4; /* 3. Use a more readable tab size (opinionated). */
36 | tab-size: 4; /* 3 */
37 | }
38 |
39 | /*
40 | Sections
41 | ========
42 | */
43 |
44 | body {
45 | margin: 0; /* Remove the margin in all browsers. */
46 | }
47 |
48 | /*
49 | Grouping content
50 | ================
51 | */
52 |
53 | /**
54 | 1. Add the correct height in Firefox.
55 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
56 | */
57 |
58 | hr {
59 | height: 0; /* 1 */
60 | color: inherit; /* 2 */
61 | }
62 |
63 | /*
64 | Text-level semantics
65 | ====================
66 | */
67 |
68 | /**
69 | Add the correct text decoration in Chrome, Edge, and Safari.
70 | */
71 |
72 | abbr[title] {
73 | text-decoration: underline dotted;
74 | }
75 |
76 | /**
77 | Add the correct font weight in Edge and Safari.
78 | */
79 |
80 | b,
81 | strong {
82 | font-weight: bolder;
83 | }
84 |
85 | /**
86 | 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
87 | 2. Correct the odd 'em' font sizing in all browsers.
88 | */
89 |
90 | code,
91 | kbd,
92 | samp,
93 | pre {
94 | font-family:
95 | ui-monospace,
96 | SFMono-Regular,
97 | Consolas,
98 | 'Liberation Mono',
99 | Menlo,
100 | monospace; /* 1 */
101 | font-size: 1em; /* 2 */
102 | }
103 |
104 | /**
105 | Add the correct font size in all browsers.
106 | */
107 |
108 | small {
109 | font-size: 80%;
110 | }
111 |
112 | /**
113 | Prevent 'sub' and 'sup' elements from affecting the line height in all browsers.
114 | */
115 |
116 | sub,
117 | sup {
118 | font-size: 75%;
119 | line-height: 0;
120 | position: relative;
121 | vertical-align: baseline;
122 | }
123 |
124 | sub {
125 | bottom: -0.25em;
126 | }
127 |
128 | sup {
129 | top: -0.5em;
130 | }
131 |
132 | /*
133 | Tabular data
134 | ============
135 | */
136 |
137 | /**
138 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
139 | 2. Correct table border color inheritance in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
140 | */
141 |
142 | table {
143 | text-indent: 0; /* 1 */
144 | border-color: inherit; /* 2 */
145 | }
146 |
147 | /*
148 | Forms
149 | =====
150 | */
151 |
152 | /**
153 | 1. Change the font styles in all browsers.
154 | 2. Remove the margin in Firefox and Safari.
155 | */
156 |
157 | button,
158 | input,
159 | optgroup,
160 | select,
161 | textarea {
162 | font-family: inherit; /* 1 */
163 | font-size: 100%; /* 1 */
164 | line-height: 1.15; /* 1 */
165 | margin: 0; /* 2 */
166 | }
167 |
168 | /**
169 | Remove the inheritance of text transform in Edge and Firefox.
170 | */
171 |
172 | button,
173 | select {
174 | text-transform: none;
175 | }
176 |
177 | /**
178 | Correct the inability to style clickable types in iOS and Safari.
179 | */
180 |
181 | button,
182 | [type='button'],
183 | [type='reset'],
184 | [type='submit'] {
185 | -webkit-appearance: button;
186 | }
187 |
188 | /**
189 | Remove the inner border and padding in Firefox.
190 | */
191 |
192 | ::-moz-focus-inner {
193 | border-style: none;
194 | padding: 0;
195 | }
196 |
197 | /**
198 | Restore the focus styles unset by the previous rule.
199 | */
200 |
201 | :-moz-focusring {
202 | outline: 1px dotted ButtonText;
203 | }
204 |
205 | /**
206 | Remove the additional ':invalid' styles in Firefox.
207 | See: https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737
208 | */
209 |
210 | :-moz-ui-invalid {
211 | box-shadow: none;
212 | }
213 |
214 | /**
215 | Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers.
216 | */
217 |
218 | legend {
219 | padding: 0;
220 | }
221 |
222 | /**
223 | Add the correct vertical alignment in Chrome and Firefox.
224 | */
225 |
226 | progress {
227 | vertical-align: baseline;
228 | }
229 |
230 | /**
231 | Correct the cursor style of increment and decrement buttons in Safari.
232 | */
233 |
234 | ::-webkit-inner-spin-button,
235 | ::-webkit-outer-spin-button {
236 | height: auto;
237 | }
238 |
239 | /**
240 | 1. Correct the odd appearance in Chrome and Safari.
241 | 2. Correct the outline style in Safari.
242 | */
243 |
244 | [type='search'] {
245 | -webkit-appearance: textfield; /* 1 */
246 | outline-offset: -2px; /* 2 */
247 | }
248 |
249 | /**
250 | Remove the inner padding in Chrome and Safari on macOS.
251 | */
252 |
253 | ::-webkit-search-decoration {
254 | -webkit-appearance: none;
255 | }
256 |
257 | /**
258 | 1. Correct the inability to style clickable types in iOS and Safari.
259 | 2. Change font properties to 'inherit' in Safari.
260 | */
261 |
262 | ::-webkit-file-upload-button {
263 | -webkit-appearance: button; /* 1 */
264 | font: inherit; /* 2 */
265 | }
266 |
267 | /*
268 | Interactive
269 | ===========
270 | */
271 |
272 | /*
273 | Add the correct display in Chrome and Safari.
274 | */
275 |
276 | summary {
277 | display: list-item;
278 | }
279 |
280 |
281 | /*
282 | Extra Recommendations from https://mattbrictson.com/blog/css-normalize-and-reset
283 | ================================================================================
284 | */
285 | :root { line-height: 1.5; }
286 | h1, h2, h3, h4, h5, figure, p, ol, ul {
287 | margin: 0;
288 | }
289 | ol, ul {
290 | list-style: none;
291 | padding-inline: 0;
292 | }
293 | h1, h2, h3, h4, h5 {
294 | font-size: inherit;
295 | font-weight: inherit;
296 | }
297 | img {
298 | display: block;
299 | max-inline-size: 100%;
300 | }
301 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/static/astro.config.mjs:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import { resolve } from 'node:path'
8 | import { env } from 'node:process'
9 | import { shield } from '@kindspells/astro-shield'
10 | import { defineConfig } from 'astro/config'
11 |
12 | const rootDir = new URL('.', import.meta.url).pathname
13 | const hashesModule = resolve(rootDir, 'src', 'generated', 'sri.mjs')
14 |
15 | // https://astro.build/config
16 | export default defineConfig({
17 | output: 'static',
18 | trailingSlash: 'always',
19 | integrations: [
20 | shield({
21 | sri: {
22 | ...((env.ENABLE_SRI_MODULE ?? 'true') === 'true'
23 | ? { hashesModule }
24 | : undefined),
25 | ...(env.ENABLE_STATIC_SRI
26 | ? { enableStatic: env.ENABLE_STATIC_SRI === 'true' }
27 | : undefined),
28 | },
29 | }),
30 | ],
31 | })
32 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/static/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "static",
3 | "version": "1.0.0",
4 | "type": "module",
5 | "scripts": {
6 | "build": "astro build",
7 | "clean": "rm -rf ./dist; rm -rf ./src/generated/*"
8 | },
9 | "license": "MIT",
10 | "dependencies": {
11 | "astro": "^4.16.8"
12 | },
13 | "devDependencies": {
14 | "@kindspells/astro-shield": "workspace:*"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/static/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/e2e/fixtures/static/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /*
3 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
4 | *
5 | * SPDX-License-Identifier: MIT
6 | */
7 |
8 | const myConst = 42
9 | ---
10 |
11 |
12 |
13 |
14 | My Static Test Site
15 |
16 |
17 |
18 |
19 | My const is: { myConst }
20 |
21 |
22 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/fs.mts:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import { readdir, stat } from 'node:fs/promises'
8 | import { resolve } from 'node:path'
9 |
10 | import type {
11 | HashesCollection,
12 | IntegrationState,
13 | Logger,
14 | SRIOptions,
15 | } from './types.mts'
16 |
17 | /** @internal */
18 | export const doesFileExist = async (path: string): Promise => {
19 | try {
20 | await stat(path)
21 | return true
22 | } catch (err) {
23 | if ((err as undefined | { code: unknown })?.code === 'ENOENT') {
24 | return false
25 | }
26 | throw err
27 | }
28 | }
29 |
30 | /** @internal */
31 | export const scanDirectory = async (
32 | logger: Logger,
33 | currentPath: string,
34 | rootPath: string,
35 | h: HashesCollection,
36 | processFile: (
37 | logger: Logger,
38 | filePath: string,
39 | distDir: string,
40 | h: HashesCollection,
41 | sri?: SRIOptions,
42 | state?: IntegrationState,
43 | ) => Promise,
44 | filenameCondition: (filename: string) => boolean,
45 | sri?: SRIOptions,
46 | state?: IntegrationState,
47 | ): Promise => {
48 | for (const file of await readdir(currentPath)) {
49 | const filePath = resolve(currentPath, file)
50 | const stats = await stat(filePath)
51 |
52 | if (stats.isDirectory()) {
53 | await scanDirectory(
54 | logger,
55 | filePath,
56 | rootPath,
57 | h,
58 | processFile,
59 | filenameCondition,
60 | sri,
61 | state,
62 | )
63 | } else if (stats.isFile() && filenameCondition(file)) {
64 | await processFile(logger, filePath, rootPath, h, sri, state)
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/headers.mts:
--------------------------------------------------------------------------------
1 | import type { CSPDirectiveNames, PerPageHashes } from './types.mts'
2 | import type {
3 | CSPDirectives,
4 | CSPOptions,
5 | SecurityHeadersOptions,
6 | } from './types.mts'
7 |
8 | export const serialiseHashes = (hashes: Set): string =>
9 | Array.from(hashes)
10 | .sort()
11 | .map(h => `'${h}'`)
12 | .join(' ')
13 |
14 | export const serializeCspDirectiveSources = (hashes: Set): string =>
15 | Array.from(hashes).sort().join(' ')
16 |
17 | export const serialiseCspDirectives = (directives: CSPDirectives): string =>
18 | Object.entries(directives)
19 | .sort()
20 | .map(([k, v]) => `${k} ${v}`)
21 | .join('; ')
22 |
23 | export const setSrcDirective = (
24 | directives: CSPDirectives,
25 | srcType: 'script-src' | 'style-src',
26 | hashes: Set,
27 | ): void => {
28 | const baseSrcDirective = directives[srcType]
29 | if (baseSrcDirective) {
30 | const srcDirective = new Set(baseSrcDirective.split(spacesRegex))
31 | for (const hash of hashes) {
32 | srcDirective.add(`'${hash}'`)
33 | }
34 | directives[srcType] = serializeCspDirectiveSources(srcDirective)
35 | } else {
36 | directives[srcType] = `'self' ${serialiseHashes(hashes)}`
37 | }
38 | }
39 |
40 | const cspSplitterRegex = /;\s*/i
41 | const spacesRegex = /\s+/i
42 |
43 | export const parseCspDirectives = (cspHeader: string): CSPDirectives => {
44 | return cspHeader
45 | ? Object.fromEntries(
46 | cspHeader
47 | .split(cspSplitterRegex)
48 | .filter(v => !!v)
49 | .map(directive => {
50 | // This is a hack to split the directive into _only_ two parts
51 | const parts = directive
52 | .replace(spacesRegex, '||||||')
53 | .split('||||||')
54 | return [parts[0] as CSPDirectiveNames, parts[1] ?? ''] satisfies [
55 | CSPDirectiveNames,
56 | string,
57 | ]
58 | }) ?? [],
59 | )
60 | : {}
61 | }
62 |
63 | export const patchCspHeader = (
64 | plainHeaders: Record,
65 | pageHashes: PerPageHashes,
66 | cspOpts: CSPOptions,
67 | ): void => {
68 | const directives = Object.hasOwn(plainHeaders, 'content-security-policy')
69 | ? {
70 | ...cspOpts.cspDirectives,
71 | ...parseCspDirectives(
72 | plainHeaders['content-security-policy'] as string,
73 | ),
74 | }
75 | : (cspOpts.cspDirectives ?? ({} satisfies CSPDirectives))
76 |
77 | if (pageHashes.scripts.size > 0) {
78 | setSrcDirective(directives, 'script-src', pageHashes.scripts)
79 | } else {
80 | directives['script-src'] = "'none'"
81 | }
82 | if (pageHashes.styles.size > 0) {
83 | setSrcDirective(directives, 'style-src', pageHashes.styles)
84 | } else {
85 | directives['style-src'] = "'none'"
86 | }
87 | if (Object.keys(directives).length > 0) {
88 | plainHeaders['content-security-policy'] = serialiseCspDirectives(directives)
89 | }
90 | }
91 |
92 | export const patchHeaders = (
93 | headers: Headers,
94 | pageHashes: PerPageHashes,
95 | securityHeadersOpts: SecurityHeadersOptions,
96 | ): Headers => {
97 | const plainHeaders = Object.fromEntries(headers.entries())
98 |
99 | if (securityHeadersOpts.contentSecurityPolicy !== undefined) {
100 | patchCspHeader(
101 | plainHeaders,
102 | pageHashes,
103 | securityHeadersOpts.contentSecurityPolicy,
104 | )
105 | }
106 |
107 | return new Headers(plainHeaders)
108 | }
109 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/main.mts:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import type { AstroIntegration } from 'astro'
8 |
9 | import { getAstroBuildDone, getAstroConfigSetup } from '#as/core'
10 | import type { IntegrationState, ShieldOptions, SRIOptions } from './types.mts'
11 |
12 | const logWarn = (msg: string): void =>
13 | console.warn(`\nWARNING (@kindspells/astro-shield):\n\t${msg}\n`)
14 |
15 | // Integration
16 | // -----------------------------------------------------------------------------
17 | export const shield = ({
18 | delayTransform,
19 | securityHeaders,
20 | sri,
21 | }: ShieldOptions): AstroIntegration => {
22 | const _sri = {
23 | enableMiddleware: sri?.enableMiddleware ?? false,
24 | enableStatic: sri?.enableStatic ?? true,
25 | hashesModule: sri?.hashesModule,
26 |
27 | allowInlineScripts: sri?.allowInlineScripts ?? 'all',
28 | allowInlineStyles: sri?.allowInlineStyles ?? 'all',
29 |
30 | scriptsAllowListUrls: sri?.scriptsAllowListUrls ?? [],
31 | stylesAllowListUrls: sri?.stylesAllowListUrls ?? [],
32 | } satisfies Required
33 |
34 | if (_sri.hashesModule && _sri.enableStatic === false) {
35 | logWarn('`sri.hashesModule` is ignored when `sri.enableStatic` is `false`')
36 | }
37 |
38 | const _delayTransform =
39 | delayTransform ??
40 | securityHeaders?.enableOnStaticPages?.provider === 'vercel'
41 |
42 | const state: IntegrationState = {
43 | delayTransform: _delayTransform,
44 | config: {},
45 | }
46 |
47 | return {
48 | name: '@kindspells/astro-shield',
49 | hooks: {
50 | 'astro:config:setup': getAstroConfigSetup(state, _sri, securityHeaders),
51 | ...(_delayTransform
52 | ? undefined
53 | : {
54 | 'astro:build:done': getAstroBuildDone(state, _sri, securityHeaders),
55 | }),
56 | },
57 | } satisfies AstroIntegration
58 | }
59 |
60 | export default shield
61 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/state.mts:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import type { MiddlewareHashes } from './types.mts'
8 |
9 | let globalHashes: MiddlewareHashes
10 |
11 | export const getGlobalHashes = (): MiddlewareHashes => {
12 | if (!globalHashes) {
13 | globalHashes = { scripts: new Map(), styles: new Map() }
14 | }
15 |
16 | return globalHashes
17 | }
18 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/tests/fixtures/fake.css:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | h1 { color: red; }
8 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/tests/fixtures/fake.js:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | alert('Test!')
8 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/tests/fixtures/nested/nested.js:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | alert('Nested!')
8 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/tests/fixtures/netlify_headers:
--------------------------------------------------------------------------------
1 | # This is a test config file
2 |
3 | /index.html
4 | # Nested Comment
5 | X-Frame-Options: DENY
6 | X-XSS-Protection: 1; mode=block
7 | /es/index.html
8 | X-Frame-Options: DENY
9 | X-XSS-Protection: 1; mode=block
10 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/tests/fixtures/vercel_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "routes": [
4 | {
5 | "src": "/es",
6 | "headers": {
7 | "Location": "/es/"
8 | },
9 | "status": 308
10 | },
11 | {
12 | "src": "/new",
13 | "headers": {
14 | "Location": "/new/"
15 | },
16 | "status": 308
17 | },
18 | {
19 | "src": "^/_astro/(.*)$",
20 | "headers": {
21 | "cache-control": "public, max-age=31536000, immutable"
22 | },
23 | "continue": true
24 | },
25 | {
26 | "handle": "filesystem"
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/tests/fs.test.mts:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import { readFile } from 'node:fs/promises'
8 | import { relative, resolve } from 'node:path'
9 |
10 | import { describe, expect, it } from 'vitest'
11 |
12 | import type { HashesCollection } from '../types.mts'
13 | import { generateSRIHash } from '../core.mts'
14 | import { doesFileExist, scanDirectory } from '../fs.mts'
15 |
16 | const testsDir = new URL('.', import.meta.url).pathname
17 |
18 | describe('doesFileExist', () => {
19 | it.each([['./core.test.mts'], ['../core.mts'], ['../main.mts']])(
20 | 'returns true for existing files',
21 | async (filename: string) => {
22 | expect(await doesFileExist(resolve(testsDir, filename))).toBe(true)
23 | },
24 | )
25 |
26 | it.each([['./magic.file'], ['../not.found'], ['../theAnswerToEverything']])(
27 | 'returns false for non-existing files',
28 | async (filename: string) => {
29 | expect(await doesFileExist(resolve(testsDir, filename))).toBe(false)
30 | },
31 | )
32 | })
33 |
34 | describe('scanDirectory', () => {
35 | it('is able to scan directories recursively', async () => {
36 | const currentDir = resolve(testsDir, 'fixtures')
37 |
38 | const h: HashesCollection = {
39 | inlineScriptHashes: new Set(),
40 | inlineStyleHashes: new Set(),
41 | extScriptHashes: new Set(),
42 | extStyleHashes: new Set(),
43 | perPageSriHashes: new Map<
44 | string,
45 | { scripts: Set; styles: Set }
46 | >(),
47 | perResourceSriHashes: {
48 | scripts: new Map(),
49 | styles: new Map(),
50 | },
51 | }
52 |
53 | await scanDirectory(
54 | console,
55 | currentDir,
56 | currentDir,
57 | h,
58 | async (_l, filepath, _dd, h) => {
59 | const content = await readFile(filepath)
60 | const hash = generateSRIHash(content)
61 | h.perResourceSriHashes.scripts.set(relative(currentDir, filepath), hash)
62 | },
63 | filename => filename.endsWith('.js'),
64 | )
65 |
66 | expect(Array.from(h.perResourceSriHashes.scripts.keys()).sort()).toEqual([
67 | 'fake.js',
68 | 'nested/nested.js',
69 | ])
70 | expect(h.perResourceSriHashes.scripts.get('fake.js')).toEqual(
71 | 'sha256-qm2QDzbth03mDFQDvyNyUc7Ctvb9qRIhKL03a5eetaY=',
72 | )
73 | expect(h.perResourceSriHashes.scripts.get('nested/nested.js')).toEqual(
74 | 'sha256-Kr4BjT3RWkTAZwxpTtuWUtdtEV+9lXy7amiQ4EXlytQ=',
75 | )
76 | })
77 | })
78 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/tests/headers.test.mts:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import { describe, expect, it } from 'vitest'
8 |
9 | import {
10 | parseCspDirectives,
11 | patchHeaders,
12 | serialiseCspDirectives,
13 | serialiseHashes,
14 | setSrcDirective,
15 | } from '../headers.mjs'
16 | import type { CSPDirectives, SecurityHeadersOptions } from '../types.mts'
17 |
18 | describe('serialiseHashes', () => {
19 | it('returns an empty string for an empty set', () => {
20 | expect(serialiseHashes(new Set())).toBe('')
21 | })
22 |
23 | it('returns a string with sorted hashes', () => {
24 | const hashes = new Set(['d', 'c', 'a', 'b'])
25 | expect(serialiseHashes(hashes)).toBe("'a' 'b' 'c' 'd'")
26 | })
27 |
28 | it('does not try to escape or remove quotes', () => {
29 | const hashes_1 = new Set(["'a'", "'b'", "'c'", "'d'"])
30 | const hashes_2 = new Set(['"a"', '"b"', '"c"', '"d"'])
31 |
32 | expect(serialiseHashes(hashes_1)).toBe("''a'' ''b'' ''c'' ''d''")
33 | expect(serialiseHashes(hashes_2)).toBe(`'"a"' '"b"' '"c"' '"d"'`)
34 | })
35 | })
36 |
37 | describe('serialiseCspDirectives', () => {
38 | it('returns an empty string for an empty object', () => {
39 | expect(serialiseCspDirectives({})).toBe('')
40 | })
41 |
42 | it('returns a string with sorted directives', () => {
43 | const directives = {
44 | 'child-src': 'a',
45 | 'connect-src': 'b',
46 | 'default-src': 'c',
47 | 'font-src': 'd',
48 | }
49 |
50 | expect(serialiseCspDirectives(directives)).toBe(
51 | 'child-src a; connect-src b; default-src c; font-src d',
52 | )
53 | })
54 | })
55 |
56 | describe('setSrcDirective', () => {
57 | it('sets the directive if it does not exist', () => {
58 | const directives: CSPDirectives = {}
59 |
60 | setSrcDirective(directives, 'script-src', new Set(['dbc1', 'xyz3', 'abc2']))
61 |
62 | expect(directives['script-src']).to.not.toBeUndefined()
63 | expect(directives['script-src']).toBe("'self' 'abc2' 'dbc1' 'xyz3'")
64 | })
65 |
66 | it('merges the directive if it exists', () => {
67 | const directives: CSPDirectives = {
68 | 'script-src': "'self' 'abc1' 'xyz2'",
69 | }
70 |
71 | setSrcDirective(
72 | directives,
73 | 'script-src',
74 | new Set(['dbc1', 'xyz3', 'abc2', 'abc1']),
75 | )
76 |
77 | expect(directives['script-src']).toBe(
78 | "'abc1' 'abc2' 'dbc1' 'self' 'xyz2' 'xyz3'",
79 | )
80 | })
81 | })
82 |
83 | describe('parseCspDirectives', () => {
84 | it('returns an empty object for an empty string', () => {
85 | expect(parseCspDirectives('')).toEqual({})
86 | })
87 |
88 | it('returns an object with parsed directives', () => {
89 | const directives = parseCspDirectives(
90 | 'child-src a1 a2; connect-src b; default-src c1 c2 c3; font-src d',
91 | )
92 |
93 | expect(directives).toEqual({
94 | 'child-src': 'a1 a2',
95 | 'connect-src': 'b',
96 | 'default-src': 'c1 c2 c3',
97 | 'font-src': 'd',
98 | })
99 | })
100 | })
101 |
102 | describe('patchHeaders', () => {
103 | it('does not set csp header if no hashes nor settings are provided', () => {
104 | const headers = new Headers()
105 | const pageHashes = { scripts: new Set(), styles: new Set() }
106 | const settings = {}
107 |
108 | const patchedHeaders = patchHeaders(headers, pageHashes, settings)
109 | expect(patchedHeaders.has('content-security-policy')).toBe(false)
110 | })
111 |
112 | it('does not set csp header if no contentSecurityPolicy option is set', () => {
113 | const headers = new Headers()
114 | const pageHashes = {
115 | scripts: new Set(['abc1', 'xyz2']),
116 | styles: new Set(['dbc1', 'xyz3', 'abc2']),
117 | }
118 | const settings: SecurityHeadersOptions = {
119 | /* contentSecurityPolicy: {} */
120 | }
121 |
122 | const patchedHeaders = patchHeaders(headers, pageHashes, settings)
123 | expect(patchedHeaders.has('content-security-policy')).toBe(false)
124 | })
125 |
126 | it('sets csp header based on settings', () => {
127 | const headers = new Headers()
128 | const pageHashes = { scripts: new Set(), styles: new Set() }
129 | const settings: SecurityHeadersOptions = {
130 | contentSecurityPolicy: {
131 | cspDirectives: {
132 | 'form-action': "'self'",
133 | 'frame-ancestors': "'none'",
134 | },
135 | },
136 | }
137 |
138 | const patchedHeaders = patchHeaders(headers, pageHashes, settings)
139 | expect(patchedHeaders.get('content-security-policy')).toBe(
140 | "form-action 'self'; frame-ancestors 'none'; script-src 'none'; style-src 'none'",
141 | )
142 | })
143 |
144 | it('sets csp header based on hashes', () => {
145 | const headers = new Headers()
146 | const pageHashes = {
147 | scripts: new Set(['abc1', 'xyz2']),
148 | styles: new Set(['dbc1', 'xyz3', 'abc2']),
149 | }
150 | const settings: SecurityHeadersOptions = { contentSecurityPolicy: {} }
151 |
152 | const patchedHeaders = patchHeaders(headers, pageHashes, settings)
153 | expect(patchedHeaders.get('content-security-policy')).toBe(
154 | "script-src 'self' 'abc1' 'xyz2'; style-src 'self' 'abc2' 'dbc1' 'xyz3'",
155 | )
156 | })
157 |
158 | it('merges existing csp header with dynamically provided hashes & config', () => {
159 | const headers = new Headers({
160 | 'content-security-policy':
161 | "base-uri 'none'; require-trusted-types-for 'script'",
162 | })
163 | const pageHashes = {
164 | scripts: new Set(['abc1', 'xyz2']),
165 | styles: new Set(['dbc1', 'xyz3', 'abc2']),
166 | }
167 | const settings: SecurityHeadersOptions = {
168 | contentSecurityPolicy: {
169 | cspDirectives: {
170 | 'form-action': "'self'",
171 | 'frame-ancestors': "'none'",
172 | },
173 | },
174 | }
175 |
176 | const patchedHeaders = patchHeaders(headers, pageHashes, settings)
177 | expect(patchedHeaders.get('content-security-policy')).toBe(
178 | "base-uri 'none'; form-action 'self'; frame-ancestors 'none'; require-trusted-types-for 'script'; script-src 'self' 'abc1' 'xyz2'; style-src 'self' 'abc2' 'dbc1' 'xyz3'",
179 | )
180 | })
181 | })
182 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/tests/main.test.mts:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import type { AstroIntegration } from 'astro'
8 | import { describe, expect, it } from 'vitest'
9 |
10 | import defaultIntegrationExport, { shield } from '../main.mts'
11 |
12 | describe('sriCSP', () => {
13 | const defaultIntegrationKeys = [
14 | 'astro:build:done',
15 | 'astro:config:setup',
16 | ] as Readonly<['astro:build:done', 'astro:config:setup']>
17 |
18 | const checkIntegration = (
19 | integration: AstroIntegration,
20 | keys: Readonly<
21 | (keyof AstroIntegration['hooks'])[]
22 | > = defaultIntegrationKeys,
23 | ) => {
24 | expect(Object.keys(integration).sort()).toEqual(['hooks', 'name'])
25 | expect(integration.name).toBe('@kindspells/astro-shield')
26 |
27 | const sortedKeys = keys.slice().sort() // TODO: use toSorted when widely available
28 | expect(Object.keys(integration.hooks).sort()).toEqual(sortedKeys)
29 | for (const key of sortedKeys) {
30 | expect(integration.hooks[key]).toBeTruthy()
31 | expect(integration.hooks[key]).toBeInstanceOf(Function)
32 | }
33 | }
34 |
35 | it('is exported as default', () => {
36 | expect(defaultIntegrationExport).toBe(shield)
37 | expect(shield).toBeInstanceOf(Function)
38 | })
39 |
40 | it('returns a valid AstroIntegration object for default config', () => {
41 | const integration = shield({})
42 | checkIntegration(integration)
43 | })
44 |
45 | it('returns a valid AstroIntegration object for almost-default config', () => {
46 | const integration = shield({ sri: { enableStatic: true } })
47 | checkIntegration(integration)
48 | })
49 |
50 | it('returns an integration even when we disable all features', () => {
51 | const integration = shield({ sri: { enableStatic: false } })
52 |
53 | // NOTE: it is too much work to verify that those hooks will do nothing
54 | checkIntegration(integration, defaultIntegrationKeys)
55 | })
56 |
57 | it('returns hooks for static & dynamic content when we enable middleware', () => {
58 | const integration = shield({ sri: { enableMiddleware: true } })
59 | checkIntegration(integration, defaultIntegrationKeys)
60 | })
61 |
62 | it('returns hooks only for dynamic content when we enable middleware and disable static sri', () => {
63 | const integration = shield({
64 | sri: {
65 | enableStatic: false,
66 | enableMiddleware: true,
67 | },
68 | })
69 | checkIntegration(integration, defaultIntegrationKeys)
70 | })
71 |
72 | it('removes build:done from base config when delayTransform=true', () => {
73 | const integration = shield({
74 | delayTransform: true,
75 | })
76 | checkIntegration(integration, ['astro:config:setup'])
77 | })
78 |
79 | it('keeps build:done in base config when delayTransform=false', () => {
80 | const integration = shield({
81 | delayTransform: false,
82 | })
83 | checkIntegration(integration, ['astro:build:done', 'astro:config:setup'])
84 | })
85 | })
86 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/tests/state.test.mts:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import { describe, expect, it } from 'vitest'
8 |
9 | import { getGlobalHashes } from '../state.mts'
10 |
11 | describe('getGlobalHashes', () => {
12 | it('returns a singleton', () => {
13 | const gh1 = getGlobalHashes()
14 | const gh2 = getGlobalHashes()
15 |
16 | expect(gh1).toBe(gh2)
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/tests/utils.test.mts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest'
2 |
3 | import { exhaustiveGuard } from '../utils.mts'
4 |
5 | describe('exhaustiveGuard', () => {
6 | it('does something', () => {
7 | expect(() => exhaustiveGuard('x' as never, 'something')).toThrowError(
8 | 'Unknown something: x',
9 | )
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/types.mts:
--------------------------------------------------------------------------------
1 | import type { AstroConfig } from 'astro'
2 |
3 | // Options
4 | // -----------------------------------------------------------------------------
5 | // We don't include 'script-src' and 'style-src' because they are handled by the
6 | // integration itself.
7 | export type CSPDirectiveNames =
8 | | 'base-uri'
9 | | 'child-src'
10 | | 'connect-src'
11 | | 'default-src'
12 | | 'font-src'
13 | | 'form-action'
14 | | 'frame-ancestors'
15 | | 'frame-src'
16 | | 'img-src'
17 | | 'manifest-src'
18 | | 'media-src'
19 | | 'object-src'
20 | | 'plugin-types'
21 | | 'prefetch-src'
22 | | 'require-trusted-types-for'
23 | | 'sandbox'
24 | | 'script-src'
25 | | 'style-src'
26 | | 'worker-src'
27 |
28 | export type CSPDirectives = { [k in CSPDirectiveNames]?: string }
29 |
30 | export type CSPOptions = {
31 | /**
32 | * - If set to `all`, the `script-src` and `style-src` directives will include
33 | * all known SRI hashes (independently of whether the associated asset is
34 | * referenced in the page or not). This can be useful to avoid problems with
35 | * the View Transitions feature.
36 | * - If set to `perPage`, the `script-src` and `style-src` directives will
37 | * include only the SRI hashes of the assets referenced in the page. This is
38 | * more secure and efficient, but it can cause problems with the View
39 | * Transitions feature.
40 | *
41 | * Defaults to `all`.
42 | */
43 |
44 | /**
45 | * - If set, it controls the "default" CSP directives (they can be overriden
46 | * at runtime).
47 | * - If not set, the middleware will use a minimal set of default directives.
48 | */
49 | cspDirectives?: CSPDirectives
50 |
51 | // TODO:
52 | // perPageCspDirectives?: Record
53 | }
54 |
55 | export type SRIOptions = {
56 | /**
57 | * When set to `true`, `@kindspells/astro-shield` will generate Subresource
58 | * Integrity (SRI) hashes for all assets referenced in static HTML pages.
59 | *
60 | * Defaults to `true`.
61 | */
62 | enableStatic?: boolean
63 |
64 | /**
65 | * When set to `true`, `@kindspells/astro-shield` will generate Subresource
66 | * Integrity (SRI) hashes for all assets referenced in dynamic pages by
67 | * enabling a middleware that will inject the SRI hashes into the generated
68 | * HTML.
69 | *
70 | * Defaults to `false`.
71 | */
72 | enableMiddleware?: boolean
73 |
74 | /**
75 | * Specifies the path for the auto-generated module that will contain the SRI
76 | * hashes. Note that:
77 | * - The generated module will be an ESM module
78 | * - The generated module should be treated as source code, and not as a build
79 | * artifact.
80 | */
81 | hashesModule?: string | undefined
82 |
83 | /**
84 | * Inline styles are usually considered unsafe because they could make it
85 | * easier for an attacker to inject CSS rules in dynamic pages. However, they
86 | * don't pose a serious security risk for _most_ static pages.
87 | *
88 | * You can disable this option in case you want to enforce a stricter policy.
89 | *
90 | * Defaults to 'all'.
91 | */
92 | allowInlineStyles?: 'all' | 'static' | false
93 |
94 | /**
95 | * Inline scripts are usually considered unsafe because they could make it
96 | * easier for an attacker to inject JS code in dynamic pages. However, they
97 | * don't pose a serious security risk for _most_ static pages.
98 | *
99 | * You can disable this option in case you want to enforce a stricter policy.
100 | *
101 | * Defaults to 'all'.
102 | */
103 | allowInlineScripts?: 'all' | 'static' | false
104 |
105 | /**
106 | * Cross-Origin scripts must be explicitly allow-listed by URL in order to be
107 | * allowed by the Content Security Policy.
108 | */
109 | scriptsAllowListUrls?: string[]
110 |
111 | /**
112 | * Cross-Origin styles must be explicitly allow-listed by URL in order to be
113 | * allowed by the Content Security Policy.
114 | */
115 | stylesAllowListUrls?: string[]
116 | }
117 |
118 | type NetlifyConfig = { provider: 'netlify' }
119 | type VercelConfig = { provider: 'vercel' }
120 |
121 | export type SecurityHeadersOptions = {
122 | enableOnStaticPages?: NetlifyConfig | VercelConfig | undefined
123 |
124 | /**
125 | * - If set, it controls how the CSP (Content Security Policy) header will be
126 | * generated in the middleware.
127 | * - If not set, no CSP header will be generated.
128 | *
129 | * Defaults to `undefined`.
130 | */
131 | contentSecurityPolicy?: CSPOptions | undefined
132 | }
133 |
134 | export type ShieldOptions = {
135 | /**
136 | * When set to `true`, the transformation of static pages will be delayed to
137 | * be executed as late as possible in the build process. This might be
138 | * necessary in case you are using many integrations that transform the HTML
139 | * output.
140 | *
141 | * If not set and any of the following conditions are met, then this option
142 | * will be automatically set to `true`:
143 | * - securityHeaders.enableOnStaticPages is set to `{ provider: 'vercel' }`
144 | *
145 | * Defaults to `false`.
146 | */
147 | delayTransform?: boolean
148 |
149 | /**
150 | * Options related to Subresource Integrity (SRI).
151 | */
152 | sri?: SRIOptions | undefined
153 |
154 | /**
155 | * - If set, it controls how the security headers will be generated in the
156 | * middleware.
157 | * - If not set, no security headers will be generated in the middleware.
158 | *
159 | * Defaults to `undefined`.
160 | */
161 | securityHeaders?: SecurityHeadersOptions | undefined
162 | }
163 |
164 | export type StrictShieldOptions = ShieldOptions & {
165 | state: IntegrationState
166 | distDir: string
167 | sri: SRIOptions & { enableStatic: boolean; enableMiddleware: boolean }
168 | }
169 |
170 | export type Logger = {
171 | info(msg: string): void
172 | warn(msg: string): void
173 | error(msg: string): void
174 | }
175 |
176 | export type MiddlewareHashes = {
177 | scripts: Map
178 | styles: Map
179 | }
180 |
181 | export type PerPageHashes = { scripts: Set; styles: Set }
182 | export type PerPageHashesCollection = Map
183 |
184 | export type HashesCollection = {
185 | inlineScriptHashes: Set
186 | inlineStyleHashes: Set
187 | extScriptHashes: Set
188 | extStyleHashes: Set
189 | perPageSriHashes: PerPageHashesCollection
190 | perResourceSriHashes: MiddlewareHashes
191 | }
192 |
193 | export type IntegrationState = {
194 | delayTransform: boolean
195 | config: Partial
196 | }
197 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/utils.mts:
--------------------------------------------------------------------------------
1 | /** @internal */
2 | export const exhaustiveGuard = (_v: never, caseName: string): void => {
3 | throw new Error(`Unknown ${caseName}: ${_v}`)
4 | }
5 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/src/vercel.mts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 | import type {
3 | CSPDirectives,
4 | Logger,
5 | PerPageHashes,
6 | PerPageHashesCollection,
7 | SecurityHeadersOptions,
8 | } from './types.mts'
9 | import { doesFileExist } from './fs.mts'
10 | import { readFile, writeFile, readdir } from 'node:fs/promises'
11 | import type { AstroConfig } from 'astro'
12 | import { serialiseCspDirectives, setSrcDirective } from './headers.mts'
13 |
14 | type VercelRoute = {
15 | src: string
16 | headers?: Record
17 | status?: number
18 | [key: string]: unknown
19 | }
20 |
21 | type VercelConfigV3 = {
22 | version: number
23 | routes?: VercelRoute[]
24 | }
25 |
26 | export type VercelConfig = VercelConfigV3
27 |
28 | const vercelAdapterDistRegexp = /\.vercel\/output\/static\/?$/
29 |
30 | export const parseVercelConfig = (
31 | logger: Logger,
32 | config: string,
33 | ): VercelConfig => {
34 | const parsed = JSON.parse(config)
35 |
36 | // TODO: Improve validation and error handling
37 | if (!('version' in parsed)) {
38 | throw new Error('Invalid Vercel config: missing "version" field')
39 | }
40 | if (parsed.version !== 3) {
41 | logger.warn(
42 | `Expected Vercel config version 3, but got version ${parsed.version}`,
43 | )
44 | }
45 |
46 | return parsed as VercelConfig
47 | }
48 |
49 | export const readVercelConfigFile = async (
50 | logger: Logger,
51 | path: string,
52 | ): Promise => {
53 | return parseVercelConfig(logger, await readFile(path, 'utf8'))
54 | }
55 |
56 | export const buildVercelConfig = (
57 | astroConfig: Partial,
58 | securityHeadersOptions: SecurityHeadersOptions,
59 | perPageSriHashes: PerPageHashesCollection,
60 | ): VercelConfig => {
61 | const indexSlashOffset =
62 | astroConfig.trailingSlash === 'never'
63 | ? -11
64 | : astroConfig.trailingSlash === 'always'
65 | ? -10
66 | : undefined
67 |
68 | const pagesToIterate: [string, PerPageHashes][] = []
69 | for (const [page, hashes] of perPageSriHashes.entries()) {
70 | if (
71 | indexSlashOffset !== undefined &&
72 | (page === 'index.html' || page.endsWith('/index.html'))
73 | ) {
74 | pagesToIterate.push([page.slice(0, indexSlashOffset), hashes])
75 | }
76 | pagesToIterate.push([page, hashes])
77 | }
78 | pagesToIterate.sort()
79 |
80 | const routes: VercelRoute[] = []
81 | for (const [page, hashes] of pagesToIterate) {
82 | const headers: Record = {}
83 |
84 | if (securityHeadersOptions.contentSecurityPolicy !== undefined) {
85 | const directives: CSPDirectives =
86 | securityHeadersOptions.contentSecurityPolicy.cspDirectives ?? {}
87 |
88 | if (hashes.scripts.size > 0) {
89 | setSrcDirective(directives, 'script-src', hashes.scripts)
90 | } else {
91 | directives['script-src'] = "'none'"
92 | }
93 | if (hashes.styles.size > 0) {
94 | setSrcDirective(directives, 'style-src', hashes.styles)
95 | } else {
96 | directives['style-src'] = "'none'"
97 | }
98 |
99 | if (Object.keys(directives).length === 0) {
100 | continue
101 | }
102 |
103 | headers['content-security-policy'] = serialiseCspDirectives(directives)
104 | }
105 |
106 | if (Object.keys(headers).length > 0) {
107 | routes.push({
108 | src: `^/${page.replaceAll('.', '\\.')}$`,
109 | headers,
110 | continue: true,
111 | })
112 | }
113 | }
114 |
115 | return { version: 3, routes }
116 | }
117 |
118 | export const mergeVercelConfig = (
119 | base: VercelConfig,
120 | patch: VercelConfig,
121 | ): VercelConfig => {
122 | return { ...base, routes: [...(patch.routes ?? []), ...(base.routes ?? [])] }
123 | }
124 |
125 | export const serializeVercelConfig = (config: VercelConfig): string => {
126 | return JSON.stringify(config, null, '\t')
127 | }
128 |
129 | export const patchVercelHeadersConfig = async (
130 | logger: Logger,
131 | distDir: string,
132 | astroConfig: Partial,
133 | securityHeadersOptions: SecurityHeadersOptions,
134 | perPageSriHashes: PerPageHashesCollection,
135 | ): Promise => {
136 | if (!vercelAdapterDistRegexp.test(distDir)) {
137 | logger.warn(
138 | '"@astrojs/vercel/static" adapter not detected, but "securityHeaders.enableOnStaticPages.provider" is set to "vercel". See https://docs.astro.build/en/guides/integrations-guide/vercel/#choosing-a-target to learn how to set up the adapter.',
139 | )
140 | return
141 | }
142 | const configPath = resolve(distDir, '..', 'config.json')
143 | if (!(await doesFileExist(configPath))) {
144 | logger.error(
145 | `Vercel adapter detected, but "config.json" not found in "${configPath}".`,
146 | )
147 | logger.error(JSON.stringify(await readdir(resolve(distDir))))
148 | logger.error(JSON.stringify(await readdir(resolve(distDir, '..'))))
149 | return
150 | }
151 |
152 | const baseConfig = await readVercelConfigFile(logger, configPath)
153 |
154 | const patchConfig = buildVercelConfig(
155 | astroConfig,
156 | securityHeadersOptions,
157 | perPageSriHashes,
158 | )
159 |
160 | const mergedConfig = mergeVercelConfig(baseConfig, patchConfig)
161 |
162 | await writeFile(configPath, serializeVercelConfig(mergedConfig))
163 | }
164 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/tsconfig.dts.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "noEmit": false,
5 | "declaration": true,
6 | "emitDeclarationOnly": true,
7 | "noCheck": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2022",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 |
7 | "baseUrl": ".",
8 | "noEmit": true,
9 | "allowImportingTsExtensions": true,
10 |
11 | "allowJs": true,
12 | "checkJs": true,
13 |
14 | "isolatedModules": true,
15 | "esModuleInterop": true,
16 | "forceConsistentCasingInFileNames": true,
17 |
18 | "strict": true,
19 | "noImplicitAny": true,
20 | "noImplicitThis": true,
21 | "useUnknownInCatchVariables": true,
22 | "alwaysStrict": true,
23 | "noUnusedLocals": true,
24 | "noUnusedParameters": true,
25 | "exactOptionalPropertyTypes": true,
26 | "noUncheckedIndexedAccess": true,
27 | "noImplicitOverride": true,
28 | "noPropertyAccessFromIndexSignature": false,
29 | "noUncheckedSideEffectImports": true,
30 |
31 | "skipLibCheck": true
32 | },
33 | "include": ["src/*", "e2e/**/*.mts"]
34 | }
35 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/vitest.config.e2e.mts:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import { defineConfig } from 'vitest/config'
8 |
9 | export default defineConfig({
10 | test: {
11 | include: ['src/e2e/**/*.test.mts'],
12 | },
13 | })
14 |
--------------------------------------------------------------------------------
/@kindspells/astro-shield/vitest.config.unit.mts:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import { defineConfig } from 'vitest/config'
8 |
9 | export default defineConfig({
10 | test: {
11 | coverage: {
12 | provider: 'v8',
13 | include: ['src/*.mts'],
14 | exclude: [
15 | 'src/tests/**/*',
16 | 'src/e2e/**/*',
17 | 'src/types.mts',
18 | 'coverage/**/*',
19 | 'coverage-e2e/**/*',
20 | 'coverage-unit/**/*',
21 | ],
22 | thresholds: {
23 | statements: 76.0,
24 | branches: 80.0,
25 | functions: 86.0,
26 | lines: 76.0,
27 | },
28 | reportsDirectory: 'coverage-unit',
29 | },
30 | include: ['src/**/tests/**/*.test.mts'],
31 | },
32 | })
33 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
6 | # Contributor Covenant Code of Conduct
7 |
8 | ## Our Pledge
9 |
10 | We as members, contributors, and leaders pledge to make participation in our
11 | community a harassment-free experience for everyone, regardless of age, body
12 | size, visible or invisible disability, ethnicity, sex characteristics, gender
13 | identity and expression, level of experience, education, socio-economic status,
14 | nationality, personal appearance, race, religion, or sexual identity
15 | and orientation.
16 |
17 | We pledge to act and interact in ways that contribute to an open, welcoming,
18 | diverse, inclusive, and healthy community.
19 |
20 | ## Our Standards
21 |
22 | Examples of behavior that contributes to a positive environment for our
23 | community include:
24 |
25 | * Demonstrating empathy and kindness toward other people
26 | * Being respectful of differing opinions, viewpoints, and experiences
27 | * Giving and gracefully accepting constructive feedback
28 | * Accepting responsibility and apologizing to those affected by our mistakes,
29 | and learning from the experience
30 | * Focusing on what is best not just for us as individuals, but for the
31 | overall community
32 |
33 | Examples of unacceptable behavior include:
34 |
35 | * The use of sexualized language or imagery, and sexual attention or
36 | advances of any kind
37 | * Trolling, insulting or derogatory comments, and personal or political attacks
38 | * Public or private harassment
39 | * Publishing others' private information, such as a physical or email
40 | address, without their explicit permission
41 | * Other conduct which could reasonably be considered inappropriate in a
42 | professional setting
43 |
44 | ## Enforcement Responsibilities
45 |
46 | Community leaders are responsible for clarifying and enforcing our standards of
47 | acceptable behavior and will take appropriate and fair corrective action in
48 | response to any behavior that they deem inappropriate, threatening, offensive,
49 | or harmful.
50 |
51 | Community leaders have the right and responsibility to remove, edit, or reject
52 | comments, commits, code, wiki edits, issues, and other contributions that are
53 | not aligned to this Code of Conduct, and will communicate reasons for moderation
54 | decisions when appropriate.
55 |
56 | ## Scope
57 |
58 | This Code of Conduct applies within all community spaces, and also applies when
59 | an individual is officially representing the community in public spaces.
60 | Examples of representing our community include using an official e-mail address,
61 | posting via an official social media account, or acting as an appointed
62 | representative at an online or offline event.
63 |
64 | ## Enforcement
65 |
66 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
67 | reported to the community leaders responsible for enforcement at
68 | contact@kindspells.dev.
69 | All complaints will be reviewed and investigated promptly and fairly.
70 |
71 | All community leaders are obligated to respect the privacy and security of the
72 | reporter of any incident.
73 |
74 | ## Enforcement Guidelines
75 |
76 | Community leaders will follow these Community Impact Guidelines in determining
77 | the consequences for any action they deem in violation of this Code of Conduct:
78 |
79 | ### 1. Correction
80 |
81 | **Community Impact**: Use of inappropriate language or other behavior deemed
82 | unprofessional or unwelcome in the community.
83 |
84 | **Consequence**: A private, written warning from community leaders, providing
85 | clarity around the nature of the violation and an explanation of why the
86 | behavior was inappropriate. A public apology may be requested.
87 |
88 | ### 2. Warning
89 |
90 | **Community Impact**: A violation through a single incident or series
91 | of actions.
92 |
93 | **Consequence**: A warning with consequences for continued behavior. No
94 | interaction with the people involved, including unsolicited interaction with
95 | those enforcing the Code of Conduct, for a specified period of time. This
96 | includes avoiding interactions in community spaces as well as external channels
97 | like social media. Violating these terms may lead to a temporary or
98 | permanent ban.
99 |
100 | ### 3. Temporary Ban
101 |
102 | **Community Impact**: A serious violation of community standards, including
103 | sustained inappropriate behavior.
104 |
105 | **Consequence**: A temporary ban from any sort of interaction or public
106 | communication with the community for a specified period of time. No public or
107 | private interaction with the people involved, including unsolicited interaction
108 | with those enforcing the Code of Conduct, is allowed during this period.
109 | Violating these terms may lead to a permanent ban.
110 |
111 | ### 4. Permanent Ban
112 |
113 | **Community Impact**: Demonstrating a pattern of violation of community
114 | standards, including sustained inappropriate behavior, harassment of an
115 | individual, or aggression toward or disparagement of classes of individuals.
116 |
117 | **Consequence**: A permanent ban from any sort of public interaction within
118 | the community.
119 |
120 | ## Attribution
121 |
122 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
123 | version 2.0, available at
124 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
125 |
126 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
127 | enforcement ladder](https://github.com/mozilla/diversity).
128 |
129 | [homepage]: https://www.contributor-covenant.org
130 |
131 | For answers to common questions about this code of conduct, see the FAQ at
132 | https://www.contributor-covenant.org/faq. Translations are available at
133 | https://www.contributor-covenant.org/translations.
134 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
6 | # Contributing Guidelines
7 |
8 | ## Code of Conduct
9 | This project and everyone participating in it is governed by our
10 | [Code of Conduct](./CODE_OF_CONDUCT.md). By participating, you are expected to
11 | uphold this code. Please report unacceptable behavior to
12 | contact@kindspells.dev.
13 |
14 | ## How can I contribute?
15 | - Reporting bugs
16 | - Proposing new features or improvements
17 | - For bug reports & proposals, consider the following:
18 | - Always be respectful, and mind the [Code of Conduct](./CODE_OF_CONDUCT.md)
19 | - Check if someone else already reported that bug or proposed that idea.
20 | - Try to be thorough and detailed with your explanations, to help others to
21 | understand them and take proper action.
22 | - Improving the current documentation
23 | - Contributing code
24 | - Always be respectful, and mind the [Code of Conduct](./CODE_OF_CONDUCT.md)
25 | - Backwards compatibility is almost sacred, please try to preserve it.
26 | - Try to respect the current coding style, to avoid style inconsistencies.
27 |
28 | ## Tooling & Workflow
29 |
30 | 1. We rely on [PNPM](https://pnpm.io/) to manage our dependencies.
31 | - Remember to always run `pnpm install` before starting to work on any
32 | project of the repository.
33 |
34 | ## Code Contributions: Acceptance Criteria
35 |
36 | In order for us to accept contributions, the merge request must fulfill certain
37 | requirements:
38 |
39 | ### Style Guide
40 |
41 | There is no "official" style guide, although we enforce style through automated
42 | tools, such as [Biome](https://biomejs.dev/)
43 |
44 | ### Commit signatures
45 | For security & regulations compliance, commits must be cryptographically signed
46 | by [PGP](https://www.openpgp.org/)/[GPG](https://gnupg.org/), or SSH
47 | ([since git v2.34](https://github.blog/2021-11-15-highlights-from-git-2-34/)).
48 | You can read more about this topic here:
49 | - [Git's documentation](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work)
50 | - [Github's documentation](https://help.github.com/en/github/authenticating-to-github/signing-commits)
51 | - [Gitlab's documentation](https://docs.gitlab.com/ee/user/project/repository/gpg_signed_commits/).
52 | - [Signing Git commits with your SSH key](https://calebhearth.com/sign-git-with-ssh)
53 |
54 | ### Commit messages
55 |
56 | Commit messages must be properly formatted (following the
57 | [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) rules).
58 | The reasons behind this decision are many:
59 | - The project's history has to be "easy" to read.
60 | - It's easier to extract statistics from the commit logs.
61 | - It's easier to generate useful changelogs.
62 | - This practice enforces that committers think twice about the nature of their
63 | contributions.
64 | - It allows us to automate version numbering (following
65 | [Semantic Versioning](https://semver.org/) rules)
66 |
67 | ### Branch history
68 |
69 | The merge request's commits have to present a "clean" history, `git rebase` is
70 | your friend. This means:
71 | - linear history
72 | - commit messages matching what the commit does
73 | - no "experimental" commits + their revert commits
74 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 KindSpells Labs S.L.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
6 | # Astro-Shield
7 |
8 | [](https://www.npmjs.com/package/@kindspells/astro-shield)
9 | 
10 | 
11 | 
12 | [](https://socket.dev/npm/package/@kindspells/astro-shield)
13 | [](https://www.bestpractices.dev/projects/8733)
14 |
15 | ## Introduction
16 |
17 | Astro-Shield helps you to enhance the security of your Astro site.
18 |
19 | ## How to install
20 |
21 | ```bash
22 | # With NPM
23 | npm install --save-dev @kindspells/astro-shield
24 |
25 | # With Yarn
26 | yarn add --dev @kindspells/astro-shield
27 |
28 | # With PNPM
29 | pnpm add --save-dev @kindspells/astro-shield
30 | ```
31 |
32 | ## How to use
33 |
34 | In your `astro.config.mjs` file:
35 |
36 | ```javascript
37 | import { defineConfig } from 'astro/config'
38 | import { shield } from '@kindspells/astro-shield'
39 |
40 | export default defineConfig({
41 | integrations: [
42 | shield({})
43 | ]
44 | })
45 | ```
46 |
47 | ## Learn more
48 |
49 | - [Astro-Shield Documentation](https://astro-shield.kindspells.dev)
50 |
51 | ## Other Relevant Guidelines
52 |
53 | - [Code of Conduct](https://github.com/KindSpells/astro-shield?tab=coc-ov-file)
54 | - [Contributing Guidelines](https://github.com/KindSpells/astro-shield/blob/main/CONTRIBUTING.md)
55 | - [Security Policy](https://github.com/KindSpells/astro-shield/security/policy)
56 |
57 | ## Main Contributors
58 |
59 | This library has been created and is being maintained by
60 | [KindSpells Labs](https://kindspells.dev/?utm_source=github&utm_medium=astro_sri_scp&utm_campaign=floss).
61 |
62 | ## License
63 |
64 | This library is released under [MIT License](https://github.com/KindSpells/astro-shield?tab=MIT-1-ov-file).
65 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
6 | # Security Policy
7 |
8 | ## Supported Versions
9 |
10 | Although we can't guarantee API stability (yet), we take every security report
11 | very seriously, and we'll do everything in our hand to respond as promptly as
12 | possible.
13 |
14 | | Version | Supported |
15 | | -------- | ------------------ |
16 | | >= 0.3 | :white_check_mark: |
17 |
18 | ## Reporting a Vulnerability
19 |
20 | You can report security vulnerabilities through our
21 | [Security Advisories section](https://github.com/KindSpells/astro-shield/security/advisories).
22 |
23 | If you want to learn more on how to report vulnerabilities, you can check these
24 | resources:
25 | - [Github: Privately reporting a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability)
26 | - [OWASP's Vulnerability Disclosure Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html)
27 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3 | "organizeImports": { "enabled": true },
4 | "files": {
5 | "include": ["*.json", "*.js", "*.mjs", "*.mts", "*.d.ts"],
6 | "ignore": [
7 | "coverage-*/**/*",
8 | "dist/**/*",
9 | "node_modules/**/*",
10 | ".sst/**/*",
11 | ".astro/**/*"
12 | ]
13 | },
14 | "linter": {
15 | "enabled": true,
16 | "rules": {
17 | "recommended": true,
18 | "correctness": {
19 | "all": true,
20 | "noNodejsModules": "off",
21 | "noUndeclaredDependencies": "off"
22 | },
23 | "performance": { "all": true },
24 | "security": { "all": true },
25 | "style": {
26 | "all": true,
27 | "useNamingConvention": "off"
28 | },
29 | "suspicious": { "all": true }
30 | }
31 | },
32 | "formatter": {
33 | "enabled": true,
34 | "formatWithErrors": false,
35 | "indentStyle": "tab",
36 | "indentWidth": 2,
37 | "lineWidth": 80,
38 | "lineEnding": "lf"
39 | },
40 | "javascript": {
41 | "formatter": {
42 | "arrowParentheses": "asNeeded",
43 | "semicolons": "asNeeded",
44 | "trailingCommas": "all",
45 | "quoteProperties": "asNeeded",
46 | "quoteStyle": "single",
47 | "jsxQuoteStyle": "single"
48 | }
49 | },
50 | "overrides": [
51 | {
52 | "include": ["*.test.mts"],
53 | "linter": {
54 | "rules": {
55 | "performance": {
56 | "useTopLevelRegex": "off"
57 | },
58 | "suspicious": {
59 | "noEmptyBlockStatements": "off",
60 | "noMisplacedAssertion": "off",
61 | "useAwait": "off"
62 | }
63 | }
64 | }
65 | },
66 | {
67 | "include": [
68 | "astro.config.mjs",
69 | "rollup.config.mjs",
70 | "vitest.config.*.mts",
71 | "src/main.mts"
72 | ],
73 | "linter": {
74 | "rules": {
75 | "style": {
76 | "noDefaultExport": "off"
77 | }
78 | }
79 | }
80 | }
81 | ]
82 | }
83 |
--------------------------------------------------------------------------------
/docs/.node-version:
--------------------------------------------------------------------------------
1 | ../.node-version
--------------------------------------------------------------------------------
/docs/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 KindSpells Labs S.L.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 |
6 | # Astro-Shield Documentation Website
7 |
8 | Just a documentation website.
9 |
--------------------------------------------------------------------------------
/docs/astro.config.mjs:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import starlight from '@astrojs/starlight'
8 | import { shield } from '@kindspells/astro-shield'
9 | import aws from 'astro-sst'
10 | import { defineConfig, passthroughImageService } from 'astro/config'
11 |
12 | const locales = {
13 | root: { lang: 'en', label: 'English' },
14 | ca: { lang: 'ca', label: 'Català' },
15 | es: { lang: 'es', label: 'Español' },
16 | hi: { lang: 'hi', label: 'हिन्दी' },
17 | ru: { lang: 'ru', label: 'Русский' },
18 | }
19 |
20 | export default defineConfig({
21 | site: 'https://astro-shield.kindspells.dev',
22 | output: 'static',
23 | adapter: aws(),
24 | trailingSlash: 'always',
25 | image: { service: passthroughImageService() },
26 | integrations: [
27 | starlight({
28 | title: 'Astro-Shield Docs',
29 | defaultLocale: 'root',
30 | locales,
31 | social: {
32 | github: 'https://github.com/kindspells/astro-shield',
33 | },
34 | sidebar: [
35 | {
36 | label: 'Start Here',
37 | translations: {
38 | ca: 'Comença Aquí',
39 | es: 'Empieza Aquí',
40 | hi: 'यहाँ से शुरू करें',
41 | ru: 'Введение',
42 | },
43 | items: [
44 | {
45 | label: 'Getting Started',
46 | translations: {
47 | ca: 'Començant',
48 | es: 'Empezando',
49 | hi: 'शुरुआत करना',
50 | ru: 'Начало работы',
51 | },
52 | link: '/getting-started/',
53 | },
54 | ],
55 | },
56 | {
57 | label: 'Guides',
58 | translations: {
59 | ca: 'Guies',
60 | es: 'Guías',
61 | hi: 'मार्गदर्शिकाएँ',
62 | ru: 'Руководства',
63 | },
64 | items: [
65 | {
66 | label: 'Subresource Integrity',
67 | autogenerate: {
68 | directory: 'guides/subresource-integrity',
69 | },
70 | },
71 | {
72 | label: 'Security Headers',
73 | translations: {
74 | ca: 'Capçaleres de Seguretat',
75 | es: 'Cabeceras de Seguridad',
76 | hi: 'सुरक्षा हेडर',
77 | ru: 'Заголовки безопасности',
78 | },
79 | autogenerate: {
80 | directory: 'guides/security-headers',
81 | },
82 | },
83 | {
84 | label: 'Hosting Integrations',
85 | translations: {
86 | ca: "Proveïdors d'Allotjament",
87 | es: 'Proveedores de Alojamiento',
88 | hi: 'होस्टिंग एकीकरण',
89 | ru: 'Интеграции хостинга',
90 | },
91 | autogenerate: {
92 | directory: 'guides/hosting-integrations',
93 | },
94 | },
95 | ],
96 | },
97 | {
98 | label: 'Reference',
99 | translations: {
100 | ca: 'Referència',
101 | es: 'Referencia',
102 | hi: 'संदर्भ',
103 | ru: 'Справка',
104 | },
105 | items: [
106 | {
107 | label: 'Configuration',
108 | translations: {
109 | ca: 'Configuració',
110 | es: 'Configuración',
111 | hi: 'कॉन्फ़िगरेशन',
112 | ru: 'Конфигурация',
113 | },
114 | link: '/reference/configuration/',
115 | },
116 | ],
117 | },
118 | {
119 | label: 'Other',
120 | translations: {
121 | ca: 'Altres',
122 | es: 'Otros',
123 | hi: 'अन्य',
124 | ru: 'Другое',
125 | },
126 | items: [
127 | {
128 | label: 'Known Limitations',
129 | translations: {
130 | ca: 'Problemes Coneguts',
131 | es: 'Problemas Conocidos',
132 | hi: 'ज्ञात सीमाएँ',
133 | ru: 'Известные ограничения',
134 | },
135 | link: '/other/known-limitations/',
136 | },
137 | {
138 | label: 'Contributing',
139 | translations: {
140 | ca: 'Contribució',
141 | es: 'Contribución',
142 | hi: 'योगदान',
143 | ru: 'Внести свой вклад',
144 | },
145 | link: 'https://github.com/kindspells/astro-shield/blob/main/CONTRIBUTING.md',
146 | },
147 | {
148 | label: 'Team & Services',
149 | translations: {
150 | ca: 'Equip i Serveis',
151 | es: 'Equipo y Servicios',
152 | hi: 'टीम और सेवाएँ',
153 | ru: 'Команда и сервисы',
154 | },
155 | link: '/other/team-services/',
156 | },
157 | ],
158 | },
159 | ],
160 | lastUpdated: true,
161 | editLink: {
162 | baseUrl: 'https://github.com/kindspells/astro-shield/edit/main/docs/',
163 | },
164 | }),
165 | shield({ sri: { enableStatic: true } }),
166 | ],
167 | build: {
168 | format: 'directory',
169 | inlineStylesheets: 'never',
170 | },
171 | })
172 |
--------------------------------------------------------------------------------
/docs/moon.yml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | type: 'application'
6 | platform: 'node'
7 |
8 | tasks:
9 | sst.install:
10 | command: 'sst install'
11 | inputs:
12 | - 'astro.config.mjs'
13 | - 'package.json'
14 | - 'sst.config.ts'
15 | outputs:
16 | - '.sst/.pulumi/meta.yaml'
17 | - '.sst/platform/version'
18 | - '.sst/stage'
19 | - '.sst/types.generated.ts'
20 | options:
21 | runInCI: false
22 | astro.check:
23 | command: 'astro check'
24 | deps:
25 | - '~:sst.install'
26 | inputs:
27 | - '.sst/types.generated.ts'
28 | - 'public/**/*'
29 | - 'src/**/*'
30 | - 'astro.config.mjs'
31 | - 'package.json'
32 | outputs:
33 | - '.astro/types.d.ts'
34 | options:
35 | runInCI: false
36 | build:
37 | command: 'astro build'
38 | deps:
39 | - '~:astro.check'
40 | inputs:
41 | - 'public/**/*'
42 | - 'src/**/*'
43 | - 'astro.config.mjs'
44 | - 'package.json'
45 | outputs:
46 | - 'dist/**/*'
47 | options:
48 | runInCI: false
49 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@kindspells/astro-shield-docs",
3 | "type": "module",
4 | "version": "1.4.0",
5 | "scripts": {
6 | "astro": "astro",
7 | "build": "moon run build",
8 | "sst:deploy": "sst deploy --stage prod",
9 | "dev": "sst dev astro dev",
10 | "preview": "astro preview",
11 | "start": "astro dev"
12 | },
13 | "dependencies": {
14 | "astro-sst": "^2.43.5",
15 | "sharp": "0.33.5",
16 | "sst": "^3.2.73"
17 | },
18 | "devDependencies": {
19 | "@astrojs/check": "^0.9.4",
20 | "@astrojs/starlight": "^0.28.5",
21 | "@astrojs/ts-plugin": "^1.10.4",
22 | "@kindspells/astro-shield": "workspace:^",
23 | "astro": "^4.16.8",
24 | "typescript": "^5.6.3"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/docs/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/src/assets/astro-shield.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kindspells/astro-shield/6cb8dff18d3eff51492466fd3029c591cd1f61df/docs/src/assets/astro-shield.webp
--------------------------------------------------------------------------------
/docs/src/content/config.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | *
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
7 | import { defineCollection } from 'astro:content';
8 | import { docsSchema } from '@astrojs/starlight/schema';
9 |
10 | export const collections = {
11 | docs: defineCollection({ schema: docsSchema() }),
12 | };
13 |
--------------------------------------------------------------------------------
/docs/src/content/docs/ca/getting-started.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Començant
7 | description: Comença a protegir els teus llocs web Astro amb Astro-Shield.
8 | ---
9 |
10 | ## Introducció
11 |
12 | Astro-Shield t'ajudarà a millorar la seguretat dels teus llocs web Astro
13 | permetent-te aplicar un conjunt ampli de bones pràctiques de seguretat, com ara:
14 | - [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)
15 | - [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
16 |
17 |
18 | ## How to install
19 |
20 | import { Code, Tabs, TabItem } from '@astrojs/starlight/components';
21 |
22 | Per a instal·lar-lo, executa la següent comanda al teu terminal:
23 |
24 |
25 |
26 | ```bash
27 | npm install --save-dev @kindspells/astro-shield
28 | ```
29 |
30 |
31 | ```bash
32 | pnpm add --save-dev @kindspells/astro-shield
33 | ```
34 |
35 |
36 | ```bash
37 | yarn add --dev @kindspells/astro-shield
38 | ```
39 |
40 |
41 |
42 | ## Activant la integració
43 |
44 | Al teu fitxer de configuració `astro.config.mjs`, importa Astro-Shield i
45 | afegeix-lo a la llista:
46 |
47 | ```js
48 | import { defineConfig } from 'astro/config'
49 | import { shield } from '@kindspells/astro-shield'
50 |
51 | export default defineConfig({
52 | integrations: [
53 | shield({})
54 | ]
55 | })
56 | ```
57 |
--------------------------------------------------------------------------------
/docs/src/content/docs/ca/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Benvingut a Astro-Shield
7 | description: Protegeix les teves pàgines web Astro amb Astro-Shield.
8 | template: splash
9 | hero:
10 | tagline: Comença a protegir la teva pàgina web amb Astro-Shield!
11 | image:
12 | file: ../../../assets/astro-shield.webp
13 | actions:
14 | - text: Començar
15 | link: /ca/getting-started/
16 | icon: right-arrow
17 | variant: primary
18 | - text: Aprèn sobre les integracions d'Astro
19 | link: https://docs.astro.build/en/guides/integrations-guide/
20 | icon: external
21 | ---
22 |
23 | import { Card, CardGrid } from '@astrojs/starlight/components';
24 |
25 | ## Què fa
26 |
27 |
28 |
29 | Astro-Shield s'encarrega de calcular els hashes SRI i de configurar l'atribut
30 | `integrity` a les etiquetes de script i estil per a tu.
31 |
32 |
33 | Astro-Shield pot configurar automàticament les capçaleres de `Content-Security-Policy`
34 | per a tu.
35 |
36 |
37 | Quan Astro-Shield detecta un script sospitós (és a dir, probablement injectat per
38 | un atacant), l'eliminarà de l'HTML renderitzat.
39 |
40 | {/*
41 | Blablablah...
42 | */}
43 |
44 |
--------------------------------------------------------------------------------
/docs/src/content/docs/es/getting-started.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Empezando
7 | description: Empieza a proteger tus sitios web Astro con Astro-Shield.
8 | ---
9 |
10 | ## Introducción
11 |
12 | Astro-Shield te ayudará a mejorar la seguridad de tu sitio Astro permitiéndote
13 | aplicar muchas de las mejores prácticas de seguridad, tales como:
14 | - [Integridad de Subrecursos](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)
15 | - [Política de Seguridad de Contenidos](https://developer.mozilla.org/es/docs/Web/HTTP/CSP)
16 |
17 | ## Cómo instalar
18 |
19 | import { Code, Tabs, TabItem } from '@astrojs/starlight/components';
20 |
21 | Para instalar, ejecuta el siguiente comando en tu terminal:
22 |
23 |
24 |
25 | ```bash
26 | npm install --save-dev @kindspells/astro-shield
27 | ```
28 |
29 |
30 | ```bash
31 | pnpm add --save-dev @kindspells/astro-shield
32 | ```
33 |
34 |
35 | ```bash
36 | yarn add --dev @kindspells/astro-shield
37 | ```
38 |
39 |
40 |
41 | ## Activando la integración
42 |
43 | En tu archivo `astro.config.mjs`, importa Astro-Shield y agrégalo a la lista
44 | de integraciones:
45 |
46 | ```js
47 | import { defineConfig } from 'astro/config'
48 | import { shield } from '@kindspells/astro-shield'
49 |
50 | export default defineConfig({
51 | integrations: [
52 | shield({})
53 | ]
54 | })
55 | ```
56 |
--------------------------------------------------------------------------------
/docs/src/content/docs/es/guides/hosting-integrations/netlify.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Netlify
7 | description: Cómo configurar Astro-Shield para que funcione en Netlify
8 | ---
9 |
10 | import { Aside, Code } from '@astrojs/starlight/components';
11 |
12 | ## `Content-Security-Policy` para contenido estático
13 |
14 | Asegurar que Netlify sirve vuestro contenido estático con las cabeceras
15 | `Content-Security-Policy` requiere algo de configuración adicional.
16 | Concretamente, hay que asignar el valor `"netlify"` para la entrada
17 | `securityHeaders.enableOnStaticPages.provider` de nuestra configuración.
18 |
19 | Aquí tenéis un ejemplo más completo:
20 |
21 | ```js
22 | import { resolve } from 'node:path'
23 |
24 | import { defineConfig } from 'astro/config'
25 | import { shield } from '@kindspells/astro-shield'
26 |
27 | const rootDir = new URL('.', import.meta.url).pathname
28 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
29 |
30 | export default defineConfig({
31 | integrations: [
32 | shield({
33 | // - Si se establece, controla cómo se generarán las cabeceras de
34 | // seguridad.
35 | // - Si no se establece, no se generarán cabeceras de seguridad.
36 | securityHeaders: {
37 | // Esta opción es necesaria para configurar las cabeceras CSP para tu
38 | // contenido estático en Netlify.
39 | enableOnStaticPages: { provider: "netlify" },
40 |
41 | // - Si se establece, controla cómo se generará la cabecera CSP
42 | // (Content Security Policy).
43 | // - Si no se establece, no se configurará ninguna cabecera CSP
44 | // para tu contenido estático (no es necesario especificar sus
45 | // opciones internas).
46 | contentSecurityPolicy: {
47 | // - Si se establece, controla las directivas CSP "por
48 | // defecto" (pueden ser sobreescritas en tiempo de ejecución).
49 | // - Si no se establece, Astro-Shield usará un conjunto mínimo
50 | // de directivas por defecto.
51 | cspDirectives: {
52 | 'default-src': "'none'",
53 | }
54 | }
55 | }
56 | })
57 | ]
58 | })
59 | ```
60 |
61 |
68 |
69 |
74 |
75 |
81 |
--------------------------------------------------------------------------------
/docs/src/content/docs/es/guides/hosting-integrations/vercel.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Vercel
7 | description: Cómo configurar Astro-Shield para que funcione en Vercel
8 | ---
9 |
10 | import { Aside, Code } from '@astrojs/starlight/components';
11 |
12 | ## `Content-Security-Policy` para contenido estático
13 |
14 | Asegurar que Vercel sirve vuestro contenido estático con las cabeceras
15 | `Content-Security-Policy` correctas requiere algo de configuración adicional.
16 | Concretamente:
17 | 1. Asignad el valor `"vercel"` a la entrada
18 | `securityHeaders.enableOnStaticPages.provider` de vuestra configuración.
19 | 2. Asignad el adaptador `@astrojs/vercel/static` (instalad el paquete
20 | `@astrojs/vercel`, podéis consultar
21 | [su documentación](https://docs.astro.build/es/guides/deploy/vercel/).
22 |
23 | Aquí tenéis un ejemplo más completo:
24 |
25 | ```js
26 | import { resolve } from 'node:path'
27 |
28 | import vercel from '@astrojs/vercel/static';
29 | import { shield } from '@kindspells/astro-shield'
30 | import { defineConfig } from 'astro/config'
31 |
32 | const rootDir = new URL('.', import.meta.url).pathname
33 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
34 |
35 | export default defineConfig({
36 | adapter: vercel(),
37 | integrations: [
38 | shield({
39 | // - Si se establece, controla cómo se generarán las cabeceras de
40 | // seguridad.
41 | // - Si no se establece, no se generarán cabeceras de seguridad.
42 | securityHeaders: {
43 | // Esta opción es necesaria para configurar las cabeceras CSP para tu
44 | // contenido estático en Vercel.
45 | enableOnStaticPages: { provider: "vercel" },
46 |
47 | // - Si se establece, controla cómo se generará la cabecera CSP
48 | // (Content Security Policy).
49 | // - Si no se establece, no se configurará ninguna cabecera CSP
50 | // para tu contenido estático (no es necesario especificar sus
51 | // opciones internas).
52 | contentSecurityPolicy: {
53 | // - Si se establece, controla las directivas CSP "por
54 | // defecto" (pueden ser sobreescritas en tiempo de ejecución).
55 | // - Si no se establece, Astro-Shield usará un conjunto mínimo
56 | // de directivas por defecto.
57 | cspDirectives: {
58 | 'default-src': "'none'",
59 | }
60 | }
61 | }
62 | })
63 | ]
64 | })
65 | ```
66 |
67 |
72 |
73 |
78 |
--------------------------------------------------------------------------------
/docs/src/content/docs/es/guides/security-headers/content-security-policy.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Content-Security-Policy (CSP)
7 | description: Cómo configurar las cabeceras Content-Security-Policy de tu sitio web con Astro-Shield
8 | ---
9 |
10 | import { Aside, Code } from '@astrojs/starlight/components';
11 |
12 | ## Activando CSP para contenido SSR
13 |
14 | Para habilitat la generación de cabeceras Content-Security-Policy para vuestro
15 | contenido SSR, tenéis que establecer la opción `securityHeaders.contentSecurityPolicy`
16 | a un objeto no nulo.
17 |
18 | Si queréis más control, entonces podéis establecer otras opciones anidadas,
19 | tales como `cspDirectives`.
20 |
21 | ```js
22 | import { resolve } from 'node:path'
23 |
24 | import { defineConfig } from 'astro/config'
25 | import { shield } from '@kindspells/astro-shield'
26 |
27 | const rootDir = new URL('.', import.meta.url).pathname
28 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
29 |
30 | export default defineConfig({
31 | integrations: [
32 | shield({
33 | sri: {
34 | // DEBE estar habilitado para páginas dinámicas!
35 | enableMiddleware: true,
36 |
37 | // CONVIENE establecerlo!
38 | hashesModule: modulePath,
39 | },
40 |
41 | // - Si se establece, controla cómo se generarán las cabeceras de
42 | // seguridad en el middleware.
43 | // - Si no se establece, no se generarán cabeceras de seguridad en
44 | // el middleware.
45 | securityHeaders: {
46 | // - Si se establece, controla cómo se generará la cabecera CSP
47 | // (Content Security Policy) en el middleware.
48 | // - Si no se establece, no se generará ninguna cabecera CSP en
49 | // el middleware. (no es necesario especificar sus opciones
50 | // internas)
51 | contentSecurityPolicy: {
52 | // - Si se establece, controla las directivas CSP "por
53 | // defecto" (pueden ser sobreescritas en tiempo de
54 | // ejecución).
55 | // - Si no se establece, el middleware usará un conjunto
56 | // mínimo de directivas por defecto.
57 | cspDirectives: {
58 | 'default-src': "'none'",
59 | }
60 | }
61 | }
62 | })
63 | ]
64 | })
65 | ```
66 |
67 |
72 |
73 |
78 |
79 |
85 |
--------------------------------------------------------------------------------
/docs/src/content/docs/es/guides/subresource-integrity/middleware.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: SRI para contenido SSR
7 | description: Cómo habilitar Subresource Integrity (SRI) para tu contenido renderizado en el servidor (SSR) en Astro.
8 | sidebar:
9 | order: 2
10 | ---
11 |
12 | import { Aside, Code } from '@astrojs/starlight/components';
13 |
14 | Por defecto, Astro-Shield no habilita SRI para contenido SSR (renderizado en el
15 | servidor), pero puedes habilitarlo fácilmente estableciendo la opción
16 | `sri.enableMiddleware` a `true` en tu archivo de configuración de Astro.
17 |
18 | ```js
19 | import { resolve } from 'node:path'
20 |
21 | import { defineConfig } from 'astro/config'
22 | import { shield } from '@kindspells/astro-shield'
23 |
24 | const rootDir = new URL('.', import.meta.url).pathname
25 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
26 |
27 | export default defineConfig({
28 | integrations: [
29 | shield({
30 | sri: {
31 | hashesModule: modulePath,
32 | enableMiddleware: true,
33 | },
34 | }),
35 | ],
36 | })
37 | ```
38 |
39 |
43 |
44 | ## Reforzando la seguridad para contenido dinámico
45 |
46 | ### Listas de permitidos
47 |
48 | Astro-Shield bloqueará cualquier recurso de origen cruzado que no esté
49 | explícitamente permitido. Esto se debe a que, de lo contrario, podría abrir la
50 | puerta a una variedad de vulnerabilidades de seguridad causadas por cargar
51 | contenido no confiable y marcarlo como seguro.
52 |
53 | Podemos definir una lista de URLs de recursos permitidos como en el siguiente
54 | ejemplo:
55 |
56 | ```js
57 | import { resolve } from 'node:path'
58 |
59 | import { defineConfig } from 'astro/config'
60 | import { shield } from '@kindspells/astro-shield'
61 |
62 | const rootDir = new URL('.', import.meta.url).pathname
63 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
64 |
65 | export default defineConfig({
66 | integrations: [
67 | shield({
68 | sri: {
69 | hashesModule: modulePath,
70 | enableMiddleware: true,
71 |
72 | scriptsAllowListUrls: [
73 | 'https://code.jquery.com/jquery-3.7.1.slim.min.js',
74 | ],
75 | stylesAllowListUrls: [
76 | 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css',
77 | ],
78 | },
79 | }),
80 | ],
81 | })
82 | ```
83 |
84 |
88 |
89 | ### Bloqueo de recursos embebidos
90 |
91 | Aunque Astro-Shield no bloquea recursos embebidos por defecto, es recomendable
92 | bloquearlos en ciertos casos para prevenir ciertos [ataques XSS](https://developer.mozilla.org/es/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss).
93 | Podemos hacerlo estableciendo las opciones `sri.allowInlineScripts` a `false` o
94 | `'static'` (este último permite recursos embebidos solo en contenido estático).
95 |
96 | ```js
97 | import { resolve } from 'node:path'
98 |
99 | import { defineConfig } from 'astro/config'
100 | import { shield } from '@kindspells/astro-shield'
101 |
102 | const rootDir = new URL('.', import.meta.url).pathname
103 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
104 |
105 | export default defineConfig({
106 | integrations: [
107 | shield({
108 | sri: {
109 | hashesModule: modulePath,
110 | enableMiddleware: true,
111 |
112 | allowInlineScripts: false,
113 | allowInlineStyles: 'static',
114 | },
115 | }),
116 | ],
117 | })
118 | ```
119 |
120 |
138 |
--------------------------------------------------------------------------------
/docs/src/content/docs/es/guides/subresource-integrity/static-sites.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: SRI para sitios generados estáticamente
7 | description: Cómo habilitar la Integridad de Subrecursos (SRI) para tus sitios web estáticos
8 | sidebar:
9 | order: 1
10 | ---
11 |
12 | import { Code, Tabs, TabItem } from '@astrojs/starlight/components';
13 |
14 | SRI está habilitado por defecto para sitios generados estáticamente. Esto
15 | significa que si encuentra scripts de JavaScript o hojas de estilo CSS, entonces
16 | calculará automáticamente sus respectivos hashes SRI y los escribirá
17 | automáticamente en el atributo `integrity` de las etiquetas `
27 | ```
28 |
29 | en
30 |
31 | ```html
32 |
33 | ```
34 |
35 |
36 | Transformará esto
37 | ```html
38 |
39 | ```
40 |
41 | en
42 |
43 | ```html
44 |
45 | ```
46 |
47 |
48 | Transformará esto
49 | ```html
50 |
51 | ```
52 |
53 | en
54 |
55 | ```html
56 |
57 | ```
58 |
59 | Observa cómo también añade el atributo `crossorigin` para mitigar el riesgo de
60 | filtrar credenciales a servidores de terceros.
61 |
62 |
63 |
64 | ## Generando el módulo de hashes SRI para recursos externos
65 |
66 | En algunos casos, es posible que necesites algunos scripts externos para acceder
67 | a los hashes SRI generados (por ejemplo, para configurar las cabeceras de un
68 | CDN). Puedes hacer esto estableciendo la propiedad `sri.hashesModule` con la
69 | ruta del módulo que exportará los hashes generados.
70 |
71 | Ejemplo:
72 | ```js
73 | import { resolve } from 'node:path'
74 |
75 | import { defineConfig } from 'astro/config'
76 | import { shield } from '@kindspells/astro-shield'
77 |
78 | const rootDir = new URL('.', import.meta.url).pathname
79 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
80 |
81 | export default defineConfig({
82 | integrations: [
83 | shield({
84 | sri: { hashesModule: modulePath },
85 | }),
86 | ],
87 | })
88 | ```
89 |
90 | Una vez que ejecutes `astro build`, el módulo generado se verá así:
91 | ```js
92 | // Do not edit this file manually
93 |
94 | export const inlineScriptHashes = /** @type {string[]} */ ([])
95 |
96 | export const inlineStyleHashes = /** @type {string[]} */ ([
97 | 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=',
98 | ])
99 |
100 | export const extScriptHashes = /** @type {string[]} */ ([
101 | 'sha256-+aSouJX5t2z1jleTbCvA9DS7+ag/F4e4ZpB/adun4Sg=',
102 | ])
103 |
104 | export const extStyleHashes = /** @type {string[]} */ ([
105 | 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=',
106 | ])
107 |
108 | export const perPageSriHashes =
109 | /** @type {Record} */ ({
110 | 'index.html': {
111 | scripts: [
112 | 'sha256-+aSouJX5t2z1jleTbCvA9DS7+ag/F4e4ZpB/adun4Sg=',
113 | ],
114 | styles: [
115 | 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk='
116 | ],
117 | },
118 | 'about.html': {
119 | scripts: [
120 | 'sha256-+aSouJX5t2z1jleTbCvA9DS7+ag/F4e4ZpB/adun4Sg=',
121 | ],
122 | styles: [
123 | 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=',
124 | ],
125 | },
126 | })
127 |
128 | export const perResourceSriHashes = {
129 | scripts: /** @type {Record} */ ({
130 | '/code.js': 'sha256-+aSouJX5t2z1jleTbCvA9DS7+ag/F4e4ZpB/adun4Sg=',
131 | }),
132 | styles: /** @type {Record} */ ({
133 | '/_astro/index.BA1ZV6fH.css':
134 | 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=',
135 | }),
136 | }
137 | ```
138 |
139 | ## Deshabilitando SRI para sitios generados estáticamente
140 |
141 | Si deseas deshabilitar SRI para sitios generados estáticamente, puedes hacerlo
142 | estableciendo la opción `sri.enableStatic` a `false` en tu archivo de
143 | configuración de Astro.
144 |
145 | ```js
146 | import { defineConfig } from 'astro/config'
147 | import { shield } from '@kindspells/astro-shield'
148 |
149 | export default defineConfig({
150 | integrations: [
151 | shield({
152 | sri: { enableStatic: false },
153 | }),
154 | ],
155 | })
156 | ```
157 |
--------------------------------------------------------------------------------
/docs/src/content/docs/es/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Bienvenido a Astro-Shield
7 | description: Protege tus sitios web Astro con Astro-Shield.
8 | template: splash
9 | hero:
10 | tagline: ¡Empieza a proteger tu sitio web con Astro-Shield!
11 | image:
12 | file: ../../../assets/astro-shield.webp
13 | actions:
14 | - text: Empezando
15 | link: /es/getting-started/
16 | icon: right-arrow
17 | variant: primary
18 | - text: Aprende sobre las integraciones de Astro
19 | link: https://docs.astro.build/en/guides/integrations-guide/
20 | icon: external
21 | ---
22 |
23 | import { Card, CardGrid } from '@astrojs/starlight/components';
24 |
25 | ## Qué hace
26 |
27 |
28 |
29 | Astro-Shield se encarga de calcular los hashes SRI y de establecer el
30 | atributo `integrity` en las etiquetas de script y estilo por ti.
31 |
32 |
33 | Astro-Shield puede establecer automáticamente los encabezados de `Content-Security-Policy`
34 | por ti.
35 |
36 |
37 | Cuando Astro-Shield detecta un script sospechoso (es decir, probablemente inyectado por
38 | un atacante), lo eliminará del HTML renderizado.
39 |
40 | {/*
41 | Blablablah...
42 | */}
43 |
44 |
--------------------------------------------------------------------------------
/docs/src/content/docs/es/other/known-limitations.md:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Limitaciones conocidas
7 | description: Limitaciones conocidas de la integración Astro-Shield.
8 | ---
9 |
10 | ## Construcción doble
11 |
12 | ⚠️ En caso de que vuestra página SSR (dinámica) incluya recursos estáticos
13 | tales como archivos `.js` o `.css`, y que alguno de estos recursos cambie,
14 | es posible que tengáis que ejecutar el comando `astro build` **dos veces
15 | seguidas** (Astro-Shield emitirá un mensaje de advertencia avisando de ello en
16 | caso que sea necesario).
17 |
18 | Cabe la posibilidad de que resolvamos este problema en el futuro, pero es
19 | importante destacar que hay algunos obstáculos técnicos que dificultan poder
20 | hacerlo de forma "elegante".
21 |
22 | ## Hot-Reloading es incapaz de regenerar los hashes SRI
23 |
24 | _Por ahora_, Astro-Shield no contiene la lógica necesaria para integrarse
25 | con el "monitor de ficheros" que permitiría regenerar los hashes SRI cuando
26 | algún archivo cambia.
27 |
28 | Esto significa que si estáis ejecutando Astro en modo de desarrollo
29 | (`astro dev`), puede ser necesario que ejecutéis manualmente el comando
30 | `astro build` para aseguraros de que los hashes SRI están debidamente
31 | actualizados y no rompen vuestra versión local de la aplicación web.
32 |
33 | ## Limitaciones de las especificaciones SRI y CSP
34 |
35 | Cuando un script is se carga mediante un import _estático_ (e.g.
36 | `import { foo } from 'https://origin.com/script.js'`) en vez de directamente
37 | mediante una etiqueta ``),
39 | tener su hash presente en la directiva CSP `script-src` no es suficiente para
40 | asegurar que el navegador lo aceptará (el navegador también "quiere" que proveas
41 | información que empareje el hash con su recurso correspondiente).
42 |
43 | Esto no es una limitación de Astro-Shield, sino una limitación resultante de
44 | combinar las especificaciones actuales de SRCI y CSP.
45 |
46 | Debido a esto, por ahora, es recomendable añadir `'self'` a la directiva
47 | `script-src` (Astro-Shield lo hace por ti).
48 |
--------------------------------------------------------------------------------
/docs/src/content/docs/es/other/team-services.md:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Equipo y Servicios
7 | description: Descubre el equipo detrás de Astro-Shield y los servicios que ofrecemos.
8 | ---
9 |
10 | Astro-Shield es un proyecto de código libre desarrollado por
11 | [KindSpells Labs](https://kindspells.dev), una pequeña empresa enfocada en
12 | desarollo de software nacida en España.
13 |
14 | ## El Equipo
15 |
16 | Nuestro equipo está formado por desarrolladores expertos con décadas acumuladas
17 | de experiencia y un alto nivel de profesionalidad.
18 |
19 | Nos apasiona crear software, y podemos decir con orgullo que lo hacemos con el
20 | mayor rigor y excelencia. Si necesitáis software de alta calidad, podéis contar
21 | con nosotros.
22 |
23 | Nuestra experiencia como desarrolladores cubre un espectro amplio de tecnologías
24 | web, incluyendo Astro, React, SolidJS, Node.js, TypeScript, y mucho más.
25 |
26 |
27 | ## Servicios
28 |
29 | - Asistencia con Astro-Shield, incluyendo instalación, configuración, y
30 | resolución de problemas.
31 | - Servicio de desarrollo a medida para Astro y otras tecnologías web (incluyendo
32 | mejoras para Astro-Shield y Astro)
33 | - Servicios de consultoría para proyectos de software y/o web.
34 |
35 | Podéis contactar con nosotros a través de nuestra
36 | [página de LinkedIn](https://www.linkedin.com/company/kindspells/).
37 |
--------------------------------------------------------------------------------
/docs/src/content/docs/getting-started.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Getting Started
7 | description: Get started protecting your Astro sites with Astro-Shield.
8 | ---
9 |
10 | ## Introduction
11 |
12 | Astro-Shield will help you enhance the security of your Astro site by allowing
13 | you to apply many security best practices, such as:
14 | - [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)
15 | - [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
16 |
17 |
18 | ## How to install
19 |
20 | import { Code, Tabs, TabItem } from '@astrojs/starlight/components';
21 |
22 | To install, run the following command in your terminal:
23 |
24 |
25 |
26 | ```bash
27 | npm install --save-dev @kindspells/astro-shield
28 | ```
29 |
30 |
31 | ```bash
32 | pnpm add --save-dev @kindspells/astro-shield
33 | ```
34 |
35 |
36 | ```bash
37 | yarn add --dev @kindspells/astro-shield
38 | ```
39 |
40 |
41 |
42 | ## Enabling the integration
43 |
44 | In your `astro.config.mjs` file, import the integration and add it to the
45 | integrations array:
46 |
47 | ```js
48 | import { defineConfig } from 'astro/config'
49 | import { shield } from '@kindspells/astro-shield'
50 |
51 | export default defineConfig({
52 | integrations: [
53 | shield({})
54 | ]
55 | })
56 | ```
57 |
--------------------------------------------------------------------------------
/docs/src/content/docs/guides/hosting-integrations/netlify.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Netlify
7 | description: How to configure Astro-Shield to work on Netlify
8 | ---
9 |
10 | import { Aside, Code } from '@astrojs/starlight/components';
11 |
12 | ## `Content-Security-Policy` for Static Content
13 |
14 | Ensuring that Netlify serves your static content with the correct
15 | `Content-Security-Policy` headers requires some additional configuration.
16 | Specifically, set `securityHeaders.enableOnStaticPages.provider` to the value
17 | `"netlify"`.
18 |
19 | See a more complete example:
20 |
21 | ```js
22 | import { resolve } from 'node:path'
23 |
24 | import { defineConfig } from 'astro/config'
25 | import { shield } from '@kindspells/astro-shield'
26 |
27 | const rootDir = new URL('.', import.meta.url).pathname
28 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
29 |
30 | export default defineConfig({
31 | integrations: [
32 | shield({
33 | // - If set, it controls how the security headers will be generated.
34 | // - If not set, no security headers will be generated.
35 | securityHeaders: {
36 | // This option is required to configure CSP headers for your static
37 | // content on Netlify.
38 | enableOnStaticPages: { provider: "netlify" },
39 |
40 | // - If set, it controls how the CSP (Content Security Policy) header
41 | // will be generated.
42 | // - If not set, no CSP header will be configured for your static
43 | // content (there is no need to specify its inner options).
44 | contentSecurityPolicy: {
45 | // - If set, it controls the "default" CSP directives (they can be
46 | // overriden at runtime).
47 | // - If not set, Astro-Shield will use a minimal set of default
48 | // directives.
49 | cspDirectives: {
50 | 'default-src': "'none'",
51 | }
52 | }
53 | }
54 | })
55 | ]
56 | })
57 | ```
58 |
59 |
66 |
67 |
71 |
72 |
78 |
--------------------------------------------------------------------------------
/docs/src/content/docs/guides/hosting-integrations/vercel.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Vercel
7 | description: How to configure Astro-Shield to work on Vercel
8 | ---
9 |
10 | import { Aside, Code } from '@astrojs/starlight/components';
11 |
12 | ## `Content-Security-Policy` for Static Content
13 |
14 | Ensuring that Vercel serves your static content with the correct
15 | `Content-Security-Policy` headers requires some additional configuration.
16 | Specifically:
17 | 1. Set `securityHeaders.enableOnStaticPages.provider` to the value
18 | `"vercel"`.
19 | 2. Set the `@astrojs/vercel/static` adapter (install the package
20 | `@astrojs/vercel`, you can check
21 | [its documentation](https://docs.astro.build/en/guides/deploy/vercel/)).
22 |
23 | See a more complete example:
24 |
25 | ```js
26 | import { resolve } from 'node:path'
27 |
28 | import vercel from '@astrojs/vercel/static';
29 | import { shield } from '@kindspells/astro-shield'
30 | import { defineConfig } from 'astro/config'
31 |
32 | const rootDir = new URL('.', import.meta.url).pathname
33 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
34 |
35 | export default defineConfig({
36 | adapter: vercel(),
37 | integrations: [
38 | shield({
39 | // - If set, it controls how the security headers will be generated.
40 | // - If not set, no security headers will be generated.
41 | securityHeaders: {
42 | // This option is required to configure CSP headers for your static
43 | // content on Vercel.
44 | enableOnStaticPages: { provider: "vercel" },
45 |
46 | // - If set, it controls how the CSP (Content Security Policy) header
47 | // will be generated.
48 | // - If not set, no CSP header will be configured for your static
49 | // content (there is no need to specify its inner options).
50 | contentSecurityPolicy: {
51 | // - If set, it controls the "default" CSP directives (they can be
52 | // overriden at runtime).
53 | // - If not set, Astro-Shield will use a minimal set of default
54 | // directives.
55 | cspDirectives: {
56 | 'default-src': "'none'",
57 | }
58 | }
59 | }
60 | })
61 | ]
62 | })
63 | ```
64 |
65 |
69 |
70 |
75 |
--------------------------------------------------------------------------------
/docs/src/content/docs/guides/security-headers/content-security-policy.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Content-Security-Policy (CSP)
7 | description: How to configure the Content-Security-Policy headers of your website with Astro-Shield
8 | ---
9 |
10 | import { Aside, Code } from '@astrojs/starlight/components';
11 |
12 | ## Enabling CSP for SSR content
13 |
14 | To enable the generation of Content-Security-Policy headers for your SSR
15 | content, you have to set the option `securityHeaders.contentSecurityPolicy` to
16 | a non-null object.
17 |
18 | If you want more control, then you can set other nested options, such as
19 | `cspDirectives`.
20 |
21 | ```js
22 | import { resolve } from 'node:path'
23 |
24 | import { defineConfig } from 'astro/config'
25 | import { shield } from '@kindspells/astro-shield'
26 |
27 | const rootDir = new URL('.', import.meta.url).pathname
28 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
29 |
30 | export default defineConfig({
31 | integrations: [
32 | shield({
33 | sri: {
34 | enableMiddleware: true, // MUST be enabled for dynamic pages!
35 | hashesModule: modulePath, // SHOULD be set!
36 | },
37 |
38 | // - If set, it controls how the security headers will be
39 | // generated in the middleware.
40 | // - If not set, no security headers will be generated in the
41 | // middleware.
42 | securityHeaders: {
43 | // - If set, it controls how the CSP (Content Security Policy)
44 | // header will be generated in the middleware.
45 | // - If not set, no CSP header will be generated in the
46 | // middleware. (there is no need to specify its inner options)
47 | contentSecurityPolicy: {
48 | // - If set, it controls the "default" CSP directives (they
49 | // can be overriden at runtime).
50 | // - If not set, the middleware will use a minimal set of
51 | // default directives.
52 | cspDirectives: {
53 | 'default-src': "'none'",
54 | }
55 | }
56 | }
57 | })
58 | ]
59 | })
60 | ```
61 |
62 |
67 |
68 |
73 |
74 |
81 |
--------------------------------------------------------------------------------
/docs/src/content/docs/guides/subresource-integrity/middleware.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: SRI for SSR Content
7 | description: How to enable Subresource Integrity (SRI) for your Server-Side-Rendered (SSR) content in Astro.
8 | sidebar:
9 | order: 2
10 | ---
11 |
12 | import { Aside, Code } from '@astrojs/starlight/components';
13 |
14 | By default, Astro-Shield does not enable SRI for SSR (Server-Side-Rendered)
15 | content, but you can easily enable it by setting the `sri.enableMiddleware`
16 | option to `true` in your Astro config file.
17 |
18 | ```js
19 | import { resolve } from 'node:path'
20 |
21 | import { defineConfig } from 'astro/config'
22 | import { shield } from '@kindspells/astro-shield'
23 |
24 | const rootDir = new URL('.', import.meta.url).pathname
25 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
26 |
27 | export default defineConfig({
28 | integrations: [
29 | shield({
30 | sri: {
31 | hashesModule: modulePath,
32 | enableMiddleware: true,
33 | },
34 | }),
35 | ],
36 | })
37 | ```
38 |
39 |
43 |
44 | ## Reinforcing security for dynamic content
45 |
46 | ### Allow Lists
47 |
48 | Astro-Shield will block any cross-origin resource that it isn't explicitly
49 | allowed. This is because doing otherwise could open the door to a variety of
50 | security vulnerabilities caused by loading untrusted content and marking it as
51 | safe.
52 |
53 | We can define a list of allowed resource URLs like in the example below:
54 |
55 | ```js
56 | import { resolve } from 'node:path'
57 |
58 | import { defineConfig } from 'astro/config'
59 | import { shield } from '@kindspells/astro-shield'
60 |
61 | const rootDir = new URL('.', import.meta.url).pathname
62 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
63 |
64 | export default defineConfig({
65 | integrations: [
66 | shield({
67 | sri: {
68 | hashesModule: modulePath,
69 | enableMiddleware: true,
70 |
71 | scriptsAllowListUrls: [
72 | 'https://code.jquery.com/jquery-3.7.1.slim.min.js',
73 | ],
74 | stylesAllowListUrls: [
75 | 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css',
76 | ],
77 | },
78 | }),
79 | ],
80 | })
81 | ```
82 |
83 |
87 |
88 | ### Blocking Inline Resources
89 |
90 | Although Astro-Shield does not block inline resources by default, it might be
91 | a good idea to block them in certain cases to prevent
92 | [XSS attacks](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss).
93 | You can do this by setting the options `sri.allowInlineScripts` and
94 | `sri.allowInlineStyles` to `false` or `'static'` (this one allows inline
95 | resources only in static content).
96 |
97 | ```js
98 | import { resolve } from 'node:path'
99 |
100 | import { defineConfig } from 'astro/config'
101 | import { shield } from '@kindspells/astro-shield'
102 |
103 | const rootDir = new URL('.', import.meta.url).pathname
104 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
105 |
106 | export default defineConfig({
107 | integrations: [
108 | shield({
109 | sri: {
110 | hashesModule: modulePath,
111 | enableMiddleware: true,
112 |
113 | allowInlineScripts: false,
114 | allowInlineStyles: 'static',
115 | },
116 | }),
117 | ],
118 | })
119 | ```
120 |
121 |
138 |
--------------------------------------------------------------------------------
/docs/src/content/docs/guides/subresource-integrity/static-sites.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: SRI for Statically Generated Sites
7 | description: How to enable Subresource Integrity (SRI) for your static sites
8 | sidebar:
9 | order: 1
10 | ---
11 |
12 | import { Code, Tabs, TabItem } from '@astrojs/starlight/components';
13 |
14 | SRI is enabled by default for statically generated sites. This means that if it
15 | encounters JavaScript scripts or CSS stylesheets then it will automatically
16 | calculate their respective SRI hashes and set them into the `integrity`
17 | attribute of `
26 | ```
27 |
28 | into
29 |
30 | ```html
31 |
32 | ```
33 |
34 |
35 | It will transform this
36 | ```html
37 |
38 | ```
39 |
40 | into
41 |
42 | ```html
43 |
44 | ```
45 |
46 |
47 | It will transform this
48 | ```html
49 |
50 | ```
51 |
52 | into
53 |
54 | ```html
55 |
56 | ```
57 |
58 | Notice how it also adds the `crossorigin` attribute to mitigate the risk of leaking credentials to third-party servers.
59 |
60 |
61 |
62 | ## Generating SRI hashes module for external scripts
63 |
64 | In some cases, you may need some external scripts to access the generated SRI
65 | hashes (e.g. to configure the headers of a CDN). You can do this by setting the
66 | `sri.hashesModule` property with the path of the module that will export the
67 | generated hashes.
68 |
69 | Example:
70 | ```js
71 | import { resolve } from 'node:path'
72 |
73 | import { defineConfig } from 'astro/config'
74 | import { shield } from '@kindspells/astro-shield'
75 |
76 | const rootDir = new URL('.', import.meta.url).pathname
77 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
78 |
79 | export default defineConfig({
80 | integrations: [
81 | shield({
82 | sri: { hashesModule: modulePath },
83 | }),
84 | ],
85 | })
86 | ```
87 |
88 | Once you run `astro build`, the generated module will look like this:
89 | ```js
90 | // Do not edit this file manually
91 |
92 | export const inlineScriptHashes = /** @type {string[]} */ ([])
93 |
94 | export const inlineStyleHashes = /** @type {string[]} */ ([
95 | 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=',
96 | ])
97 |
98 | export const extScriptHashes = /** @type {string[]} */ ([
99 | 'sha256-+aSouJX5t2z1jleTbCvA9DS7+ag/F4e4ZpB/adun4Sg=',
100 | ])
101 |
102 | export const extStyleHashes = /** @type {string[]} */ ([
103 | 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=',
104 | ])
105 |
106 | export const perPageSriHashes =
107 | /** @type {Record} */ ({
108 | 'index.html': {
109 | scripts: [
110 | 'sha256-+aSouJX5t2z1jleTbCvA9DS7+ag/F4e4ZpB/adun4Sg=',
111 | ],
112 | styles: [
113 | 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk='
114 | ],
115 | },
116 | 'about.html': {
117 | scripts: [
118 | 'sha256-+aSouJX5t2z1jleTbCvA9DS7+ag/F4e4ZpB/adun4Sg=',
119 | ],
120 | styles: [
121 | 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=',
122 | ],
123 | },
124 | })
125 |
126 | export const perResourceSriHashes = {
127 | scripts: /** @type {Record} */ ({
128 | '/code.js': 'sha256-+aSouJX5t2z1jleTbCvA9DS7+ag/F4e4ZpB/adun4Sg=',
129 | }),
130 | styles: /** @type {Record} */ ({
131 | '/_astro/index.BA1ZV6fH.css':
132 | 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=',
133 | }),
134 | }
135 | ```
136 |
137 | ## Disabling SRI for Statically Generated Sites
138 |
139 | If you want to disable SRI for statically generated sites, you can do so by
140 | setting the `sri.enableStatic` option to `false` in your Astro config file.
141 |
142 | ```js
143 | import { defineConfig } from 'astro/config'
144 | import { shield } from '@kindspells/astro-shield'
145 |
146 | export default defineConfig({
147 | integrations: [
148 | shield({
149 | sri: { enableStatic: false },
150 | }),
151 | ],
152 | })
153 | ```
154 |
--------------------------------------------------------------------------------
/docs/src/content/docs/hi/getting-started.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 | title: शुरुआत करना
6 | description: अपनी आस्ट्रो वेबसाइटों को आस्ट्रो-शील्ड के साथ सुरक्षित करना शुरू करें।
7 | ---
8 |
9 | ## परिचय
10 |
11 | आस्ट्रो-शील्ड आपको अपनी आस्ट्रो वेबसाइटों की सुरक्षा में सुधार करने में मदद करेगा,
12 | जो आपको सुरक्षा के व्यापक सर्वोत्तम अभ्यासों को लागू करने की अनुमति देता है, जैसे:
13 |
14 | - [सबरिसोर्स इंटेग्रिटी](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)
15 | - [कंटेंट सिक्योरिटी पॉलिसी](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
16 |
17 | ## कैसे इंस्टॉल करें
18 |
19 | import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
20 |
21 | इसे इंस्टॉल करने के लिए, अपने टर्मिनल में निम्नलिखित कमांड चलाएँ:
22 |
23 |
24 |
25 | ```bash npm install --save-dev @kindspells/astro-shield ```
26 |
27 |
28 | ```bash pnpm add --save-dev @kindspells/astro-shield ```
29 |
30 |
31 | ```bash yarn add --dev @kindspells/astro-shield ```
32 |
33 |
34 |
35 | ## एकीकरण सक्रिय करना
36 |
37 | अपनी `astro.config.mjs` कॉन्फ़िगरेशन फ़ाइल में, आस्ट्रो-शील्ड को आयात करें और
38 | इसे सूची में जोड़ें:
39 |
40 | ```js
41 | import { defineConfig } from "astro/config";
42 | import { shield } from "@kindspells/astro-shield";
43 |
44 | export default defineConfig({
45 | integrations: [shield({})],
46 | });
47 | ```
48 |
--------------------------------------------------------------------------------
/docs/src/content/docs/hi/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: आस्ट्रो-शील्ड में आपका स्वागत है
7 | description: आस्ट्रो-शील्ड के साथ अपनी आस्ट्रो साइटों की सुरक्षा करें।
8 | template: splash
9 | hero:
10 | tagline: आस्ट्रो-शील्ड के साथ अपनी साइट की सुरक्षा शुरू करें!
11 | image:
12 | file: ../../../assets/astro-shield.webp
13 | actions:
14 | - text: शुरुआत करें
15 | link: /hi/getting-started/
16 | icon: right-arrow
17 | variant: primary
18 | - text: आस्ट्रो एकीकरण के बारे में जानें
19 | link: https://docs.astro.build/en/guides/integrations-guide/
20 | icon: external
21 | ---
22 |
23 | import { Card, CardGrid } from "@astrojs/starlight/components";
24 |
25 | ## यह क्या करता है
26 |
27 |
28 |
29 | आस्ट्रो-शील्ड आपके लिए SRI हैश की गणना करने और स्क्रिप्ट और स्टाइल टैग पर
30 | `integrity` विशेषता सेट करने का ध्यान रखता है।
31 |
32 |
33 | आस्ट्रो-शील्ड आपके लिए स्वचालित रूप से `Content-Security-Policy` हेडर सेट कर
34 | सकता है।
35 |
36 |
37 | जब आस्ट्रो-शील्ड एक संदिग्ध स्क्रिप्ट का पता लगाता है (यानी, संभवतः एक
38 | हमलावर द्वारा इंजेक्ट की गई), तो यह उसे रेंडर किए गए HTML से हटा देगा।
39 |
40 | {/*
41 | Blablablah...
42 | */}
43 |
44 |
--------------------------------------------------------------------------------
/docs/src/content/docs/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Welcome to Astro-Shield
7 | description: Protect your Astro sites with Astro-Shield.
8 | template: splash
9 | hero:
10 | tagline: Start protecting your site with Astro-Shield!
11 | image:
12 | file: ../../assets/astro-shield.webp
13 | actions:
14 | - text: Getting Started
15 | link: /getting-started/
16 | icon: right-arrow
17 | variant: primary
18 | - text: Learn about Astro integrations
19 | link: https://docs.astro.build/en/guides/integrations-guide/
20 | icon: external
21 | ---
22 |
23 | import { Card, CardGrid } from '@astrojs/starlight/components';
24 |
25 | ## What it does
26 |
27 |
28 |
29 | Astro-Shield takes care of calculating the SRI hashes and setting the
30 | `integrity` attribute on the script and style tags for you.
31 |
32 |
33 | Astro-Shield can automatically set the `Content-Security-Policy` headers
34 | for you.
35 |
36 |
37 | When Astro-Shield detects a suspicious script (that is, likely injected by
38 | an attacker), it will remove it from the rendered HTML.
39 |
40 | {/*
41 | Blablablah...
42 | */}
43 |
44 |
--------------------------------------------------------------------------------
/docs/src/content/docs/other/known-limitations.md:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Known Limitations
7 | description: Known limitations of the Astro-Shield integration.
8 | ---
9 |
10 | ## Double Build
11 |
12 | ⚠️ In case your SSR (dynamic) pages refer to static `.js` or `.css` files, and
13 | any of these resources change, then you might have to run the `astro build`
14 | command **two consecutive times** (Astro-Shield will emit a warning message
15 | telling you about it in case it is needed).
16 |
17 | We might try to improve this in the future, but there are some technical issues
18 | that make it hard to solve this problem in an elegant way.
19 |
20 | ## Missing File Watcher
21 |
22 | _For now_, Astro-Shield does not provide file watcher logic that would
23 | automatically regenerate the SRI hashes when files change.
24 |
25 | This means that if you are running Astro in development mode (`astro dev`), you
26 | might have to manually run `astro build` to avoid having stale SRI hashes that
27 | break your local version of the site.
28 |
29 | ## SRI & CSP spec limitations
30 |
31 | When a script is loaded with a _static_ import (e.g.
32 | `import { foo } from 'https://origin.com/script.js'`) rather than directly
33 | included with a ``), having
35 | its hash present in the `script-src` CSP directive is not enough to ensure that
36 | the browser will accept it (the browser also wants you to provide information
37 | that pairs the hash with a specific resource).
38 |
39 | This, in itself, is not a limitation of Astro-Shield, but rather a limitation of
40 | the combination of current SRI and CSP specs.
41 |
42 | Because of that, for now, it is advisable to add `'self'` to the `script-src`
43 | directive (Astro-Shield does it for you).
44 |
--------------------------------------------------------------------------------
/docs/src/content/docs/other/team-services.md:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Team & Services
7 | description: Discover the team behind Astro-Shield and the services we offer.
8 | ---
9 |
10 | Astro-Shield is an open-source project developed by
11 | [KindSpells Labs](https://kindspells.dev), a small team of experienced software
12 | developers based in Spain.
13 |
14 | ## The Team
15 |
16 | We are passionate about software development, and we take pride in delivering
17 | high-quality software solutions to our clients.
18 |
19 | Our team has experience in a wide range of web technologies, including Astro,
20 | React, SolidJS, Node.js, TypeScript, and more.
21 |
22 | ## Services
23 |
24 | - Assistance with Astro-Shield, including installation, configuration, and
25 | troubleshooting.
26 | - Custom development services for Astro and other web technologies (including
27 | improvements to Astro-Shield).
28 | - Consulting services for web & software development projects.
29 |
30 | You can reach out to us via our
31 | [LinkedIn page](https://www.linkedin.com/company/kindspells/).
32 |
--------------------------------------------------------------------------------
/docs/src/content/docs/reference/configuration.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Configuration Reference
7 | description: An overview of all the configuration options Astro-Shield supports.
8 | ---
9 |
10 | import { Aside } from '@astrojs/starlight/components';
11 |
12 | ## Configure the `astro-shield` integration
13 |
14 | Astro-Shield is an integration built on top the [Astro](https://astro.build) web
15 | framework. We can configure our project inside the `astro.config.mjs`
16 | configuration file:
17 |
18 | ```js
19 | // astro.config.mjs
20 | import { defineConfig } from 'astro/config'
21 | import { shield } from '@kindspells/astro-shield'
22 |
23 | export default defineConfig({
24 | integrations: [
25 | shield({}),
26 | ],
27 | })
28 | ```
29 |
30 | You can pass the following options to the `@kindspells/astro-shield`
31 | integration.
32 |
33 | ### `sri`
34 |
35 | The `sri` option allows us to configure the settings that control how
36 | Astro-Shield will handle Subresource Integrity (SRI) for our project.
37 |
38 | Type: `object | undefined`, defaults to `undefined`.
39 |
40 | Example:
41 |
42 | ```js
43 | import { resolve } from 'node:path'
44 |
45 | import { defineConfig } from 'astro/config'
46 | import { shield } from '@kindspells/astro-shield'
47 |
48 | const rootDir = new URL('.', import.meta.url).pathname
49 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
50 |
51 | export default defineConfig({
52 | integrations: [
53 | shield({
54 | sri: {
55 | enableMiddleware: true,
56 | hashesModule: modulePath,
57 | allowInlineScripts: false,
58 | allowInlineStyles: false,
59 | scriptsAllowListUrls: [
60 | 'https://example.com/script.js',
61 | ],
62 | },
63 | }),
64 | ],
65 | })
66 | ```
67 |
68 | ### `sri.allowInlineScripts`
69 |
70 | Specifies whether Astro-Shield should allow inline scripts (and how). Its
71 | possible values are:
72 | - `'all'` *(default)*: Allow all inline scripts in all pages.
73 | - `'static'`: Allow inline scripts only in static pages.
74 | - `false`: Disallow all inline scripts.
75 |
76 | ### `sri.allowInlineStyles`
77 |
78 | Specifies whether Astro-Shield should allow inline styles (and how). Its
79 | possible values are:
80 | - `'all'` *(default)*: Allow all inline styles in all pages.
81 | - `'static'`: Allow inline styles only in static pages.
82 | - `false`: Disallow all inline styles.
83 |
84 | ### `sri.enableMiddleware`
85 |
86 | Specifies whether Astro-Shield should enable the middleware to compute the SRI
87 | hashes for our dynamic content.
88 |
89 | It is also necessary in case we need to generate the Content-Security-Policy
90 | (CSP) headers for our dynamic content.
91 |
92 | Type: `boolean`, defaults to `false`.
93 |
94 |
98 |
99 | ### `sri.enableStatic`
100 |
101 | Specifies whether Astro-Shield should generate the SRI hashes for our static
102 | content.
103 |
104 | Type: `boolean`, defaults to `true`.
105 |
106 | ### `sri.hashesModule`
107 |
108 | Specifies the path to the autogenerated module that contains and exports the SRI
109 | hashes computed by Astro-Shield for our content.
110 |
111 | We can import this module into our own code in case we need to implement any
112 | custom logic that requires the SRI hashes, but its main purpose is to be used
113 | together with the [`sri.enableMiddleware`](#srienablemiddleware) option.
114 |
115 | Type: `string | undefined`, defaults to `undefined`.
116 |
117 | ### `sri.scriptsAllowListUrls`
118 |
119 | Cross-origin scripts must be explicitly allow-listed by URL so that the content
120 | security policies enforced via CSP headers do not block them.
121 |
122 | This options allows us to define a list of script URLs that are allowed to be
123 | loaded in our pages.
124 |
125 | Type: `string[]`, defaults to `[]`.
126 |
127 | ### `sri.stylesAllowListUrls`
128 |
129 | Cross-origin stylesheets must be explicitly allow-listed by URL so that the
130 | content security policies enforced via CSP headers do not block them.
131 |
132 | This options allows us to define a list of stylesheet URLs that are allowed to
133 | be loaded in our pages.
134 |
135 | Type: `string[]`, defaults to `[]`.
136 |
137 | ### `securityHeaders`
138 |
139 | The `securityHeaders` option allows us to configure the settings that control
140 | how Astro-Shield will generate the security headers for our project.
141 |
142 | If set, it controls how the security headers will be generated for our project,
143 | otherwise no security headers will be generated.
144 |
145 | Type: `object | undefined`, defaults to `undefined`.
146 |
147 | Example:
148 |
149 | ```js
150 | // astro.config.mjs
151 | import { defineConfig } from 'astro/config'
152 | import { shield } from '@kindspells/astro-shield'
153 |
154 | export default defineConfig({
155 | integrations: [
156 | shield({
157 | securityHeaders: {
158 | enableOnStaticPages: { provider: 'netlify' },
159 | contentSecurityPolicy: {
160 | cspDirectives: {
161 | 'default-src': "'none'",
162 | },
163 | },
164 | },
165 | }),
166 | ],
167 | })
168 | ```
169 |
170 | ### `securityHeaders.contentSecurityPolicy`
171 |
172 | If set, it controls how the Content-Security-Policy (CSP) header will be
173 | generated (for our static and/or dynamic content).
174 |
175 | If not set, no CSP header will be configured for our content.
176 |
177 | Type: `object | undefined`, defaults to `undefined`.
178 |
179 | #### `securityHeaders.contentSecurityPolicy.cspDirectives`
180 |
181 | If set, it controls the "default" CSP directives (they can be overriden at
182 | runtime).
183 |
184 | If not set, Astro-Shield will use a minimal set of default directives.
185 |
186 | Type: `Record`, defaults to `{}`.
187 |
188 | ### `securityHeaders.enableOnStaticPages`
189 |
190 | Specifies whether Astro-Shield should "generate" security headers for our static
191 | content.
192 |
193 | If set, it controls how the security headers will be generated for our static
194 | content, otherwise no security headers will be generated for it.
195 |
196 | Type: `object | undefined`, defaults to `undefined`.
197 |
198 | #### `securityHeaders.enableOnStaticPages.provider`
199 |
200 | Possible values are:
201 | - `'netlify'`: Generate the security headers for static content on Netlify.
202 |
--------------------------------------------------------------------------------
/docs/src/content/docs/ru/getting-started.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Начало работы
7 | description: Начните защищать ваш Astro сайт с помощью Astro-Shield.
8 | ---
9 |
10 | ## Вступление
11 |
12 | Astro-Shield поможет вам повысить безопасность вашего сайта Astro, позволяя применять многие лучшие практики безопасности, как например:
13 | - [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) (целостность подресурса)
14 | - [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (политика безопасности контента)
15 |
16 |
17 | ## Установка
18 |
19 | import { Code, Tabs, TabItem } from '@astrojs/starlight/components';
20 |
21 | Для установки выполните следующую команду в вашем терминале:
22 |
23 |
24 |
25 | ```bash
26 | npm install --save-dev @kindspells/astro-shield
27 | ```
28 |
29 |
30 | ```bash
31 | pnpm add --save-dev @kindspells/astro-shield
32 | ```
33 |
34 |
35 | ```bash
36 | yarn add --dev @kindspells/astro-shield
37 | ```
38 |
39 |
40 |
41 | ## Включение интеграции
42 |
43 | В вашем `astro.config.mjs` файле импортируйте интеграцию и добавьте её в массив интеграций:
44 |
45 | ```js
46 | import { defineConfig } from 'astro/config'
47 | import { shield } from '@kindspells/astro-shield'
48 |
49 | export default defineConfig({
50 | integrations: [
51 | shield({})
52 | ]
53 | })
54 | ```
55 |
--------------------------------------------------------------------------------
/docs/src/content/docs/ru/guides/hosting-integrations/netlify.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Netlify
7 | description: Как настроить Astro-Shield для работы в Netlify
8 | ---
9 |
10 | import { Aside, Code } from '@astrojs/starlight/components';
11 |
12 | ## `Content-Security-Policy` для статического контента
13 |
14 | Чтобы Netlify обслуживал ваш статический контент с правильными заголовками
15 | `Content-Security-Policy`, требуется дополнительная настройка. В частности,
16 | установите `securityHeaders.enableOnStaticPages.provider` в значение `"netlify"`.
17 |
18 | Рассмотрим полный пример:
19 |
20 | ```js
21 | import { resolve } from 'node:path'
22 |
23 | import { defineConfig } from 'astro/config'
24 | import { shield } from '@kindspells/astro-shield'
25 |
26 | const rootDir = new URL('.', import.meta.url).pathname
27 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
28 |
29 | export default defineConfig({
30 | integrations: [
31 | shield({
32 | // - Если установлено, то контролирует, как будут генерироваться заголовки безопасности.
33 | // - Если не установлено, то заголовки безопасности не будут генерироваться.
34 | securityHeaders: {
35 | // Эта опция необходима для настройки заголовков CSP для вашего статического
36 | // контента на Netlify.
37 | enableOnStaticPages: { provider: "netlify" },
38 |
39 | // - Если установлено, то контролирует, как будет генерироваться заголовок
40 | // CSP (Content Security Policy).
41 | // - Если не установлено, то заголовок CSP не будет настроен для вашего
42 | // статического контента (нет необходимости указывать его внутренние параметры).
43 | contentSecurityPolicy: {
44 | // - Если установлено, контролирует значения по умолчанию директивы CSP
45 | // (они могут быть переопределены во время выполнения).
46 | // - Если не установлено, Astro-Shield будет использовать минимальный
47 | // набор директив по умолчанию.
48 | cspDirectives: {
49 | 'default-src': "'none'",
50 | }
51 | }
52 | }
53 | })
54 | ]
55 | })
56 | ```
57 |
58 |
65 |
66 |
70 |
71 |
77 |
--------------------------------------------------------------------------------
/docs/src/content/docs/ru/guides/hosting-integrations/vercel.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Vercel
7 | description: Как настроить Astro-Shield для работы в Vercel
8 | ---
9 |
10 | import { Aside, Code } from '@astrojs/starlight/components';
11 |
12 | ## `Content-Security-Policy` для статического контента
13 |
14 | Чтобы Vercel обслуживал ваш статический контент с правильными заголовками `Content-Security-Policy`, требуется дополнительная настройка.
15 | А именно:
16 | 1. Установите `securityHeaders.enableOnStaticPages.provider` в значение `"vercel"`.
17 | 2. Установите адаптер `@astrojs/vercel/static` (установите пакет `@astrojs/vercel`, который вы можете проверить
18 | [в его документации](https://docs.astro.build/en/guides/deploy/vercel/)).
19 |
20 | Рассмотрим полный пример:
21 |
22 | ```js
23 | import { resolve } from 'node:path'
24 |
25 | import vercel from '@astrojs/vercel/static';
26 | import { shield } from '@kindspells/astro-shield'
27 | import { defineConfig } from 'astro/config'
28 |
29 | const rootDir = new URL('.', import.meta.url).pathname
30 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
31 |
32 | export default defineConfig({
33 | adapter: vercel(),
34 | integrations: [
35 | shield({
36 | // - Если установлено, то контролирует, как будут генерироваться заголовки безопасности.
37 | // - Если не установлено, то заголовки безопасности не будут генерироваться.
38 | securityHeaders: {
39 | // Эта опция необходима для настройки заголовков CSP для вашего статического
40 | // контента на Vercel.
41 | enableOnStaticPages: { provider: "vercel" },
42 |
43 | // - Если установлено, то контролирует, как будет генерироваться заголовок
44 | // CSP (Content Security Policy).
45 | // - Если не установлено, то заголовок CSP не будет настроен для вашего
46 | // статического контента (нет необходимости указывать его внутренние параметры).
47 | contentSecurityPolicy: {
48 | // - Если установлено, контролирует значения по умолчанию директивы CSP
49 | // (они могут быть переопределены во время выполнения).
50 | // - Если не установлено, Astro-Shield будет использовать минимальный
51 | // набор директив по умолчанию.
52 | cspDirectives: {
53 | 'default-src': "'none'",
54 | }
55 | }
56 | }
57 | })
58 | ]
59 | })
60 | ```
61 |
62 |
66 |
67 |
72 |
--------------------------------------------------------------------------------
/docs/src/content/docs/ru/guides/security-headers/content-security-policy.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Content-Security-Policy (CSP)
7 | description: Как настроить заголовки Content-Security-Policy вашего сайта с помощью Astro-Shield
8 | ---
9 |
10 | import { Aside, Code } from '@astrojs/starlight/components';
11 |
12 | ## Включение CSP для SSR контента
13 |
14 | Чтобы включить генерацию заголовков Content-Security-Policy для вашего SSR контента,
15 | необходимо установить опцию `securityHeaders.contentSecurityPolicy` в ненулевой объект.
16 |
17 | Если вам нужно больше контроля, вы можете установить другие вложенные параметры,
18 | такие как `cspDirectives`.
19 |
20 | ```js
21 | import { resolve } from 'node:path'
22 |
23 | import { defineConfig } from 'astro/config'
24 | import { shield } from '@kindspells/astro-shield'
25 |
26 | const rootDir = new URL('.', import.meta.url).pathname
27 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
28 |
29 | export default defineConfig({
30 | integrations: [
31 | shield({
32 | sri: {
33 | enableMiddleware: true, // ДОЛЖНО быть включено для динамических страниц!
34 | hashesModule: modulePath, // ДОЛЖНО быть установлено!
35 | },
36 |
37 | // - Если установлено, то контролирует, как заголовки безопасности будут
38 | // генерироваться в middleware.
39 | // - Если не установлено, то заголовки безопасности не будут генерироваться
40 | // в middleware.
41 | securityHeaders: {
42 | // - Если установлено, то контролирует, как заголовок CSP (Content Security Policy)
43 | // будет генерироваться в middleware.
44 | // - Если не установлено, то заголовок CSP не будет генерироваться в
45 | // middleware.
46 | // (нет необходимости указывать его вложенные параметры)
47 | contentSecurityPolicy: {
48 | // - Если установлено, то контролирует "по умолчанию" директивы CSP
49 | // (они могут быть переопределены во время выполнения).
50 | // - Если не установлено, то middleware будет использовать минимальный
51 | // набор директив по умолчанию.
52 | cspDirectives: {
53 | 'default-src': "'none'",
54 | }
55 | }
56 | }
57 | })
58 | ]
59 | })
60 | ```
61 |
62 |
67 |
68 |
73 |
74 |
81 |
--------------------------------------------------------------------------------
/docs/src/content/docs/ru/guides/subresource-integrity/middleware.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: SRI для SSR контента
7 | description: Как включить Subresource Integrity (SRI) для вашего контента при рендеринга на стороне сервера (SSR) в Astro.
8 | sidebar:
9 | order: 2
10 | ---
11 |
12 | import { Aside, Code } from '@astrojs/starlight/components';
13 |
14 | По умолчанию, Astro-Shield не включает SRI для контента,
15 | отрендеренного на стороне сервера (Server-Site-Rendering, SSR), но вы можете легко включить его,
16 | установив опцию `sri.enableMiddleware` в `true` в вашем конфигурационном файле Astro.
17 |
18 | ```js
19 | import { resolve } from 'node:path'
20 |
21 | import { defineConfig } from 'astro/config'
22 | import { shield } from '@kindspells/astro-shield'
23 |
24 | const rootDir = new URL('.', import.meta.url).pathname
25 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
26 |
27 | export default defineConfig({
28 | integrations: [
29 | shield({
30 | sri: {
31 | hashesModule: modulePath,
32 | enableMiddleware: true,
33 | },
34 | }),
35 | ],
36 | })
37 | ```
38 |
39 |
43 |
44 | ## Усиление безопасности для динамического контента
45 |
46 | ### Списки разрешенных ресурсов
47 |
48 | Astro-Shield будет блокировать любые кросс-доменные ресурсы, которые не разрешены явно.
49 | В противном случае это может открыть возможности для различных уязвимостей безопасности,
50 | вызванных загрузкой ненадежного контента и его пометкой как безопасного.
51 |
52 | Мы можем определить список разрешенных URL-адресов ресурсов, как в примере ниже:
53 |
54 | ```js
55 | import { resolve } from 'node:path'
56 |
57 | import { defineConfig } from 'astro/config'
58 | import { shield } from '@kindspells/astro-shield'
59 |
60 | const rootDir = new URL('.', import.meta.url).pathname
61 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
62 |
63 | export default defineConfig({
64 | integrations: [
65 | shield({
66 | sri: {
67 | hashesModule: modulePath,
68 | enableMiddleware: true,
69 |
70 | scriptsAllowListUrls: [
71 | 'https://code.jquery.com/jquery-3.7.1.slim.min.js',
72 | ],
73 | stylesAllowListUrls: [
74 | 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css',
75 | ],
76 | },
77 | }),
78 | ],
79 | })
80 | ```
81 |
82 |
86 |
87 | ### Блокировка inline-ресурсов
88 |
89 | Хотя Astro-Shield по умолчанию не блокирует inline-ресурсы,
90 | в некоторых случаях это может быть хорошей идеей, чтобы предотвратить
91 | [XSS атаки](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss).
92 | Вы можете сделать это установив опции `sri.allowInlineScripts` и
93 | `sri.allowInlineStyles` в `false` или `'static'` (этот параметр позволяет использовать inline-ресурсы
94 | только в статическом контенте).
95 |
96 | ```js
97 | import { resolve } from 'node:path'
98 |
99 | import { defineConfig } from 'astro/config'
100 | import { shield } from '@kindspells/astro-shield'
101 |
102 | const rootDir = new URL('.', import.meta.url).pathname
103 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
104 |
105 | export default defineConfig({
106 | integrations: [
107 | shield({
108 | sri: {
109 | hashesModule: modulePath,
110 | enableMiddleware: true,
111 |
112 | allowInlineScripts: false,
113 | allowInlineStyles: 'static',
114 | },
115 | }),
116 | ],
117 | })
118 | ```
119 |
120 |
137 |
--------------------------------------------------------------------------------
/docs/src/content/docs/ru/guides/subresource-integrity/static-sites.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: SRI для SSG контента
7 | description: Как включить Subresource Integrity (SRI) для ваших статически сгенерированных сайтов.
8 | sidebar:
9 | order: 1
10 | ---
11 |
12 | import { Code, Tabs, TabItem } from '@astrojs/starlight/components';
13 |
14 | SRI включен по умолчанию для статически сгенерированных сайтов (Static-Site-Generation, SSG).
15 | Это означает, что если он обнаружит скрипты JavaScript или таблицы стилей CSS,
16 | то автоматически вычислит их соответствующие хеши SRI и установит их
17 | в атрибут `integrity` тегов `
26 | ```
27 |
28 | в
29 |
30 | ```html
31 |
32 | ```
33 |
34 |
35 | Он преобразует это
36 | ```html
37 |
38 | ```
39 |
40 | в
41 |
42 | ```html
43 |
44 | ```
45 |
46 |
47 | Он преобразует это
48 | ```html
49 |
50 | ```
51 |
52 | в
53 |
54 | ```html
55 |
56 | ```
57 |
58 | Обратите внимание, что он также добавляет атрибут `crossorigin`, чтобы уменьшить риск утечки учетных данных на сторонние серверы.
59 |
60 |
61 |
62 | ## Отключение SRI для статически сгенерированных сайтов
63 |
64 | Если вы хотите отключить SRI для статически сгенерированных сайтов, вы можете сделать это, установив опцию `sri.enableStatic` в `false` в вашем конфигурационном файле Astro.
65 | ```js
66 | import { resolve } from 'node:path'
67 |
68 | import { defineConfig } from 'astro/config'
69 | import { shield } from '@kindspells/astro-shield'
70 |
71 | const rootDir = new URL('.', import.meta.url).pathname
72 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
73 |
74 | export default defineConfig({
75 | integrations: [
76 | shield({
77 | sri: { hashesModule: modulePath },
78 | }),
79 | ],
80 | })
81 | ```
82 |
83 | После запуска команды `astro build` сгенерированный модуль будет выглядеть следующим образом:
84 | ```js
85 | // Do not edit this file manually
86 |
87 | export const inlineScriptHashes = /** @type {string[]} */ ([])
88 |
89 | export const inlineStyleHashes = /** @type {string[]} */ ([
90 | 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=',
91 | ])
92 |
93 | export const extScriptHashes = /** @type {string[]} */ ([
94 | 'sha256-+aSouJX5t2z1jleTbCvA9DS7+ag/F4e4ZpB/adun4Sg=',
95 | ])
96 |
97 | export const extStyleHashes = /** @type {string[]} */ ([
98 | 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=',
99 | ])
100 |
101 | export const perPageSriHashes =
102 | /** @type {Record} */ ({
103 | 'index.html': {
104 | scripts: [
105 | 'sha256-+aSouJX5t2z1jleTbCvA9DS7+ag/F4e4ZpB/adun4Sg=',
106 | ],
107 | styles: [
108 | 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk='
109 | ],
110 | },
111 | 'about.html': {
112 | scripts: [
113 | 'sha256-+aSouJX5t2z1jleTbCvA9DS7+ag/F4e4ZpB/adun4Sg=',
114 | ],
115 | styles: [
116 | 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=',
117 | ],
118 | },
119 | })
120 |
121 | export const perResourceSriHashes = {
122 | scripts: /** @type {Record} */ ({
123 | '/code.js': 'sha256-+aSouJX5t2z1jleTbCvA9DS7+ag/F4e4ZpB/adun4Sg=',
124 | }),
125 | styles: /** @type {Record} */ ({
126 | '/_astro/index.BA1ZV6fH.css':
127 | 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=',
128 | }),
129 | }
130 | ```
131 |
132 | ## Отключение SRI для статически сгенерированных сайтов
133 |
134 | Если вы хотите отключить SRI для статически сгенерированных сайтов,
135 | вы можете сделать это, установив опцию `sri.enableStatic` в `false`
136 | в вашем конфигурационном файле Astro.
137 |
138 | ```js
139 | import { defineConfig } from 'astro/config'
140 | import { shield } from '@kindspells/astro-shield'
141 |
142 | export default defineConfig({
143 | integrations: [
144 | shield({
145 | sri: { enableStatic: false },
146 | }),
147 | ],
148 | })
149 | ```
150 |
--------------------------------------------------------------------------------
/docs/src/content/docs/ru/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Добро пожаловать в Astro-Shield
7 | description: Защищайте свои Astro сайты с помощью Astro-Shield.
8 | template: splash
9 | hero:
10 | tagline: Начините защищать свой сайт с помощью Astro-Shield!
11 | image:
12 | file: ../../../assets/astro-shield.webp
13 | actions:
14 | - text: Начать работу
15 | link: /ru/getting-started/
16 | icon: right-arrow
17 | variant: primary
18 | - text: Узнать об интеграциях Astro
19 | link: https://docs.astro.build/en/guides/integrations-guide/
20 | icon: external
21 | ---
22 |
23 | import { Card, CardGrid } from '@astrojs/starlight/components';
24 |
25 | ## Что делает Astro-Shield?
26 |
27 |
28 |
29 | Astro-Shield берет на себя расчет хэшей SRI и установку атрибута `integrity` для тегов script и style за вас.
30 |
31 |
32 | Astro-Shield может автоматически выставлять заголовки `Content-Security-Policy` за вас.
33 |
34 |
35 | Когда Astro-Shield обнаруживает вредоносный скрипт (который, например, мог быть вставлен злоумышленником), то он удаляет его из отрендеренного HTML.
36 |
37 | {/*
38 | Blablablah...
39 | */}
40 |
41 |
--------------------------------------------------------------------------------
/docs/src/content/docs/ru/other/known-limitations.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Известные ограничения
7 | description: Известные ограничения интеграции Astro-Shield.
8 | ---
9 |
10 | ## Двойная сборка
11 |
12 | ⚠️ В случае, если ваши SSR (динамические) страницы ссылаются на статические
13 | файлы `.js` или `.css`, и любой из этих ресурсов изменяется, вам, возможно,
14 | придется запустить команду `astro build` **два раза подряд** (Astro-Shield
15 | выдаст предупреждающее сообщение, если это потребуется).
16 |
17 | Мы можем попытаться улучшить это в будущем, но существуют некоторые технические
18 | проблемы, которые затрудняют решение этой задачи элегантным способом.
19 |
20 |
21 | ## Отсутствие File Watcher
22 |
23 | _На данный момент_ Astro-Shield не предоставляет логику наблюдателя за файлами,
24 | которая бы автоматически регенерировала хеши SRI при изменении файлов.
25 |
26 | Это означает, что если вы запускаете Astro в режиме разработки (`astro dev`),
27 | вам, возможно, придется вручную запускать `astro build`, чтобы избежать устаревших
28 | хешей SRI, которые могут нарушить работу локальной версии сайта.
29 |
30 | ## Ограничения спецификаций SRI и CSP
31 |
32 | Когда скрипт загружается с помощью _статического_ импорта (например,
33 | `import { foo } from 'https://origin.com/script.js'`), а не напрямую
34 | включается с помощью тега ``), наличие
36 | его хеша в директиве `script-src` CSP недостаточно для того, чтобы браузер
37 | принял его (браузер также требует, чтобы вы предоставили информацию, которая
38 | связывает хеш с конкретным ресурсом).
39 |
40 | Это само по себе не является ограничением Astro-Shield, а скорее ограничением
41 | комбинации текущих спецификаций SRI и CSP.
42 |
43 | Из-за этого, на данный момент, рекомендуется добавить `'self'` в директиву
44 | `script-src` (Astro-Shield делает это за вас).
45 |
--------------------------------------------------------------------------------
/docs/src/content/docs/ru/other/team-services.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Команда & Сервисы
7 | description: Познакомьтесь с командой, стоящей за Astro-Shield, и сервисами, которые мы предлагаем.
8 | ---
9 |
10 | Astro-Shield — это проект с открытым исходным кодом, разработанный
11 | [KindSpells Labs](https://kindspells.dev), небольшой командой опытных
12 | разработчиков программного обеспечения из Испании.
13 |
14 | ## Команда
15 |
16 | Мы увлечены разработкой программного обеспечения и гордимся тем,
17 | что предоставляем нашим клиентам высококачественные программные решения.
18 |
19 | Наша команда имеет опыт работы с широким спектром веб-технологий,
20 | включая Astro, React, SolidJS, Node.js, TypeScript и другие.
21 |
22 | ## Сервисы
23 |
24 | - Помощь с Astro-Shield, включая установку, настройку и устранение неполадок.
25 | - Услуги по разработке на заказ для Astro и других веб-технологий (включая улучшения для Astro-Shield).
26 | - Консультационные услуги для web и программных проектов.
27 |
28 | Вы можете связаться с нами через наш
29 | [LinkedIn](https://www.linkedin.com/company/kindspells/).
30 |
--------------------------------------------------------------------------------
/docs/src/content/docs/ru/reference/configuration.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
3 | #
4 | # SPDX-License-Identifier: MIT
5 |
6 | title: Справка по конфигурации
7 | description: Обзор всех конфигурационных настроет, поддерживаемых Astro-Shield.
8 | ---
9 |
10 | import { Aside } from '@astrojs/starlight/components';
11 |
12 | ## Настройка интеграции `astro-shield`
13 |
14 | Astro-Shield — это интеграция, построенная поверх веб-фреймворка
15 | [Astro](https://astro.build). Мы можем настроить наш проект в
16 | конфигурационном файле `astro.config.mjs`:
17 |
18 | ```js
19 | // astro.config.mjs
20 | import { defineConfig } from 'astro/config'
21 | import { shield } from '@kindspells/astro-shield'
22 |
23 | export default defineConfig({
24 | integrations: [
25 | shield({}),
26 | ],
27 | })
28 | ```
29 |
30 | Вы можете передать следующие опции в настройки интеграции `@kindspells/astro-shield`.
31 |
32 | ### `sri`
33 |
34 | Опция `sri` позволяет нам настроить параметры, которые контролируют,
35 | как Astro-Shield будет обрабатывать целостность подресурсов (SRI) для нашего проекта.
36 |
37 | Тип: `object | undefined`, по умолчанию `undefined`.
38 |
39 | Пример:
40 |
41 | ```js
42 | import { resolve } from 'node:path'
43 |
44 | import { defineConfig } from 'astro/config'
45 | import { shield } from '@kindspells/astro-shield'
46 |
47 | const rootDir = new URL('.', import.meta.url).pathname
48 | const modulePath = resolve(rootDir, 'src', 'generated', 'sriHashes.mjs')
49 |
50 | export default defineConfig({
51 | integrations: [
52 | shield({
53 | sri: {
54 | enableMiddleware: true,
55 | hashesModule: modulePath,
56 | allowInlineScripts: false,
57 | allowInlineStyles: false,
58 | scriptsAllowListUrls: [
59 | 'https://example.com/script.js',
60 | ],
61 | },
62 | }),
63 | ],
64 | })
65 | ```
66 |
67 | ### `sri.allowInlineScripts`
68 |
69 | Определяет, должен ли Astro-Shield разрешать встроенные скрипты (и каким образом).
70 | Возможные значения:
71 | - `'all'` *(по умолчанию)*: Разрешить все встроенные скрипты на всех страницах.
72 | - `'static'`: Разрешить встроенные скрипты только на статических страницах.
73 | - `false`: Запретить все встроенные скрипты.
74 |
75 | ### `sri.allowInlineStyles`
76 |
77 | Определяет, должен ли Astro-Shield разрешать встроенные стили (и каким образом).
78 | Возможные значения:
79 | - `'all'` *(по умолчанию)*: Разрешить все встроенные стили на всех страницах.
80 | - `'static'`: Разрешить встроенные стили только на статических страницах.
81 | - `false`: Запретить все встроенные стили.
82 |
83 | ### `sri.enableMiddleware`
84 |
85 | Определяет, должен ли Astro-Shield включать middleware для вычисления хешей SRI
86 | для динамического контента.
87 |
88 | Это также необходимо в случае, если нам нужно сгенерировать заголовки
89 | Content-Security-Policy (CSP) для динамического контента.
90 |
91 | Тип: `boolean`, по умолчанию `false`.
92 |
93 |
97 |
98 | ### `sri.enableStatic`
99 |
100 | Определяет, должен ли Astro-Shield генерировать хеши SRI для статического контента.
101 |
102 | Тип: `boolean`, по умолчанию `true`.
103 |
104 | ### `sri.hashesModule`
105 |
106 | Указывает путь к автоматически сгенерированному модулю, который содержит и
107 | кспортирует хеши SRI, вычисленные Astro-Shield для нашего контента.
108 |
109 | Мы можем импортировать этот модуль в наш собственный код, если нам нужно
110 | реализовать какую-либо пользовательскую логику, требующую хеши SRI, но его
111 | основное назначение — использоваться вместе с опцией [`sri.enableMiddleware`](#srienablemiddleware).
112 |
113 | Тип: `string | undefined`, по умолчанию `undefined`.
114 |
115 | ### `sri.scriptsAllowListUrls`
116 |
117 | Скрипты с другого источника должны быть явно добавлены в белый список по
118 | URL, чтобы политики безопасности контента, применяемые через заголовки CSP,
119 | не блокировали их.
120 |
121 | Эта опция позволяет нам определить список URL-адресов скриптов, которые
122 | разрешено загружать на страницах.
123 |
124 | Тип: `string[]`, по умолчанию `[]`.
125 |
126 | ### `sri.stylesAllowListUrls`
127 |
128 | Таблицы стилей с другого источника должны быть явно добавлены в белый список
129 | по URL, чтобы политики безопасности контента, применяемые через заголовки CSP,
130 | не блокировали их.
131 |
132 | Эта опция позволяет нам определить список URL-адресов таблиц стилей, которые
133 | разрешено загружать на страницах.
134 |
135 | Тип: `string[]`, по умолчанию `[]`.
136 |
137 | ### `securityHeaders`
138 |
139 | Опция `securityHeaders` позволяет нам настроить параметры, которые контролируют,
140 | как Astro-Shield будет генерировать заголовки безопасности для нашего проекта.
141 |
142 | Если установлено, то будет контролировать как генерируются заголовки безопасности
143 | для проекта, в противном случае заголовки безопасности генерироваться не будут.
144 |
145 | Тип: `object | undefined`, по умолчанию `undefined`.
146 |
147 | Пример:
148 |
149 | ```js
150 | // astro.config.mjs
151 | import { defineConfig } from 'astro/config'
152 | import { shield } from '@kindspells/astro-shield'
153 |
154 | export default defineConfig({
155 | integrations: [
156 | shield({
157 | securityHeaders: {
158 | enableOnStaticPages: { provider: 'netlify' },
159 | contentSecurityPolicy: {
160 | cspDirectives: {
161 | 'default-src': "'none'",
162 | },
163 | },
164 | },
165 | }),
166 | ],
167 | })
168 | ```
169 |
170 | ### `securityHeaders.contentSecurityPolicy`
171 |
172 | Если установлено, то контролирует, как будет генерироваться заголовок
173 | Content-Security-Policy (CSP) для статического и/или динамического контента.
174 |
175 | Если не установлено, заголовок CSP не будет настроен для контента.
176 |
177 | Тип: `object | undefined`, по умолчанию `undefined`.
178 |
179 | #### `securityHeaders.contentSecurityPolicy.cspDirectives`
180 |
181 | Если установлено, контролирует значение по умолчанию директивы CSP
182 | (они могут быть переопределены во время выполнения).
183 |
184 | Если не установлено, то Astro-Shield будет использовать минимальный
185 | набор директив по умолчанию.
186 |
187 | Тип: `Record`, по умолчанию `{}`.
188 |
189 | ### `securityHeaders.enableOnStaticPages`
190 |
191 | Указывает, должен ли Astro-Shield "генерировать" заголовки безопасности
192 | для статического контента.
193 |
194 | Если установлено, то контролирует, как будут генерироваться заголовки
195 | безопасности для статического контента, в противном случае заголовки
196 | безопасности генерироваться не будут.
197 |
198 | Тип: `object | undefined`, по умолчанию `undefined`.
199 |
200 | #### `securityHeaders.enableOnStaticPages.provider`
201 |
202 | Возможные значения:
203 | - `'netlify'`: генерировать заголовки безопасности для статического контента на Netlify.
204 |
--------------------------------------------------------------------------------
/docs/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/docs/src/sst-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/docs/sst-env.d.ts:
--------------------------------------------------------------------------------
1 | /* This file is auto-generated by SST. Do not edit. */
2 | /* tslint:disable */
3 | /* eslint-disable */
4 | import 'sst'
5 | export {}
6 | declare module 'sst' {
7 | export interface Resource {
8 | AstroShield: {
9 | type: 'sst.aws.Astro'
10 | url: string
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/docs/sst.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | /*
4 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
5 | *
6 | * SPDX-License-Identifier: MIT
7 | */
8 |
9 | export default $config({
10 | app(input) {
11 | return {
12 | name: "docs",
13 | removal: input?.stage === "production" ? "retain" : "remove",
14 | home: "aws",
15 | };
16 | },
17 | async run() {
18 | new sst.aws.Astro("AstroShield", {
19 | domain: {
20 | dns: sst.aws.dns(),
21 | name: 'astro-shield.kindspells.dev'
22 | }
23 | });
24 | },
25 | });
26 |
--------------------------------------------------------------------------------
/docs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | /*
3 | * SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
4 | *
5 | * SPDX-License-Identifier: MIT
6 | */
7 | "extends": "astro/tsconfigs/strictest",
8 | "exclude": ["dist/**/*", "node_modules/**/*"],
9 | "compilerOptions": {
10 | "plugins": [{ "name": "@astrojs/ts-plugin" }]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@kindspells/root",
3 | "private": true,
4 | "version": "1.0.0",
5 | "license": "MIT",
6 | "contributors": [
7 | {
8 | "name": "Andres Correa Casablanca",
9 | "url": "https://blog.coderspirit.xyz/about/"
10 | }
11 | ],
12 | "devDependencies": {
13 | "@biomejs/biome": "^1.9.4",
14 | "@moonrepo/cli": "^1.29.3",
15 | "@vitest/coverage-v8": "^2.1.4",
16 | "publint": "^0.2.12",
17 | "vitest": "^2.1.4"
18 | },
19 | "engines": {
20 | "node": ">= 22.9.0"
21 | },
22 | "packageManager": "pnpm@9.12.0",
23 | "scripts": {
24 | "format": "biome format --write .",
25 | "install-githooks": "if [ -d .git ]; then git config core.hooksPath .hooks; fi"
26 | },
27 | "pnpm": {
28 | "onlyBuiltDependencies": ["@moonrepo/cli"]
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 KindSpells Labs S.L.
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | packages:
6 | - 'docs'
7 | - '@kindspells/*'
8 | - '@kindspells/astro-shield/src/e2e/fixtures/*'
9 |
--------------------------------------------------------------------------------