├── .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 | [![NPM Version](https://img.shields.io/npm/v/%40kindspells%2Fastro-shield)](https://www.npmjs.com/package/@kindspells/astro-shield) 9 | ![NPM Downloads](https://img.shields.io/npm/dw/%40kindspells%2Fastro-shield) 10 | ![GitHub commit activity](https://img.shields.io/github/commit-activity/w/kindspells/astro-shield) 11 | ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/kindspells/astro-shield/tests.yml) 12 | [![Socket Badge](https://socket.dev/api/badge/npm/package/@kindspells/astro-shield)](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 | [![NPM Version](https://img.shields.io/npm/v/%40kindspells%2Fastro-shield)](https://www.npmjs.com/package/@kindspells/astro-shield) 9 | ![NPM Downloads](https://img.shields.io/npm/dw/%40kindspells%2Fastro-shield) 10 | ![GitHub commit activity](https://img.shields.io/github/commit-activity/w/kindspells/astro-shield) 11 | ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/kindspells/astro-shield/tests.yml) 12 | [![Socket Badge](https://socket.dev/api/badge/npm/package/@kindspells/astro-shield)](https://socket.dev/npm/package/@kindspells/astro-shield) 13 | [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8733/badge)](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 | --------------------------------------------------------------------------------