abqari 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -1
- data/README.md +9 -8
- data/lib/abqari/audit/csp.rb +178 -0
- data/lib/abqari/audit.rb +4 -2
- data/lib/abqari/cli/headers.rb +1 -14
- data/lib/abqari/headers_file.rb +18 -0
- data/lib/abqari/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac44a7f76fcb30cb218104f9e002a0cd958518542912393d7681fceab92abdf0
|
|
4
|
+
data.tar.gz: 8a0cc5fe35425ca6571dbc76549df5105106bf8ff672f67ea9101c26e48af550
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9050a56ac582b3cdb3f4590bf90f6543ee57fa0dd6e1c4b56afc7274a937dfbb805400869d157473019af1a9802da7e88af97e30961f436cbed8a2472f2bb6cb
|
|
7
|
+
data.tar.gz: db4d7cb9c339676e76cc794b8e205fb0000d50266d8b28ae6a53770b7d701a44fd3664d3f3d2076e3fc78cd2f08745ab442a4abd85a5bcbdbb0ef14586a8f26b
|
data/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
_Nothing yet._
|
|
11
11
|
|
|
12
|
+
## [1.3.0] — 2026-10-11
|
|
13
|
+
|
|
14
|
+
If CI runs `bin/audit --strict`, run `bin/audit` locally before
|
|
15
|
+
upgrading: a new Security check can fail an audit that passed on
|
|
16
|
+
1.2.0. Every finding is inline code that is already blocked on any host
|
|
17
|
+
that enforces the site's CSP, so the fix is in the site, not the
|
|
18
|
+
policy.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **`bin/audit` now fails on inline code the site's own CSP would
|
|
23
|
+
block.** The generated policy has `style-src 'self'` and pins inline
|
|
24
|
+
scripts by hash, so a template that adds `style="…"`, a `<style>`
|
|
25
|
+
block, an `onclick=`, or an unhashed `<script>` breaks on any host
|
|
26
|
+
that enforces the CSP. It hid because nothing local enforces it: the
|
|
27
|
+
dev server doesn't apply `_headers`, the page renders, and the
|
|
28
|
+
browser only logs a console error. One site had 52 inline
|
|
29
|
+
`style="--ratio: …"` attributes that would have collapsed every photo
|
|
30
|
+
pane the moment its CSP went live.
|
|
31
|
+
|
|
32
|
+
The new **Security** category (`Audit::Csp`) checks every page
|
|
33
|
+
against the CSP in the built `_headers`, so a `headers:` override or
|
|
34
|
+
`csp:` extension is what's checked. It reports one error per page per
|
|
35
|
+
kind of inline code, with an example and how to fix it. It follows
|
|
36
|
+
the spec: hashes, `'unsafe-inline'` (ignored once a hash or nonce is
|
|
37
|
+
present), `'unsafe-hashes'` for attributes, the `-elem`/`-attr`
|
|
38
|
+
directives and the `default-src` fallback. `bin/audit --strict` fails
|
|
39
|
+
on these, so a site that passed yesterday can fail today; every
|
|
40
|
+
finding is code that is already broken wherever the policy is
|
|
41
|
+
applied.
|
|
42
|
+
|
|
12
43
|
## [1.2.0] — 2026-10-11
|
|
13
44
|
|
|
14
45
|
Upgrade if you serve the generated CSP: Chrome has been blocking
|
|
@@ -1110,7 +1141,8 @@ run a real working author / publisher site.
|
|
|
1110
1141
|
commonmarker ≥ 2.4 ships precompiled 4.0 binaries, which is what
|
|
1111
1142
|
the `~> 2.4` floor bought.
|
|
1112
1143
|
|
|
1113
|
-
[Unreleased]: https://github.com/grantrayner/abqari/compare/v1.
|
|
1144
|
+
[Unreleased]: https://github.com/grantrayner/abqari/compare/v1.3.0...HEAD
|
|
1145
|
+
[1.3.0]: https://github.com/grantrayner/abqari/compare/v1.2.0...v1.3.0
|
|
1114
1146
|
[1.2.0]: https://github.com/grantrayner/abqari/compare/v1.1.2...v1.2.0
|
|
1115
1147
|
[1.1.2]: https://github.com/grantrayner/abqari/compare/v1.1.1...v1.1.2
|
|
1116
1148
|
[1.1.1]: https://github.com/grantrayner/abqari/compare/v1.1.0...v1.1.1
|
data/README.md
CHANGED
|
@@ -11,7 +11,7 @@ optional overrides), while the engine — layouts, themes, helpers, asset
|
|
|
11
11
|
pipeline — lives in the dependency. Forking the engine is also
|
|
12
12
|
supported for users who want to own all the code.
|
|
13
13
|
|
|
14
|
-
> **Status:** v1.
|
|
14
|
+
> **Status:** v1.3.0 — see
|
|
15
15
|
> [CHANGELOG.md](https://github.com/grantrayner/abqari/blob/main/CHANGELOG.md).
|
|
16
16
|
> Distributed via RubyGems (`gem install abqari`) and as a Bundler
|
|
17
17
|
> path/git source. The API follows [semver](https://semver.org/) from
|
|
@@ -174,7 +174,7 @@ Test that it worked:
|
|
|
174
174
|
abqari version
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
You should see a version number, e.g. `abqari 1.
|
|
177
|
+
You should see a version number, e.g. `abqari 1.3.0`. If you get
|
|
178
178
|
`command not found`, see [Troubleshooting](#troubleshooting) below.
|
|
179
179
|
|
|
180
180
|
### Step 3 — Create your first site
|
|
@@ -460,7 +460,7 @@ Edit `Gemfile`:
|
|
|
460
460
|
```ruby
|
|
461
461
|
source 'https://rubygems.org'
|
|
462
462
|
|
|
463
|
-
gem 'abqari', '~> 1.
|
|
463
|
+
gem 'abqari', '~> 1.3'
|
|
464
464
|
|
|
465
465
|
group :development do
|
|
466
466
|
gem 'listen', '~> 3.8' # optional, for the dev-server file watcher
|
|
@@ -470,7 +470,7 @@ end
|
|
|
470
470
|
Or — pin to a git ref while tracking pre-release work:
|
|
471
471
|
|
|
472
472
|
```ruby
|
|
473
|
-
gem 'abqari', git: 'https://github.com/grantrayner/abqari.git', tag: 'v1.
|
|
473
|
+
gem 'abqari', git: 'https://github.com/grantrayner/abqari.git', tag: 'v1.3.0'
|
|
474
474
|
```
|
|
475
475
|
|
|
476
476
|
Install the libvips C library once (hard dep for the image pipeline):
|
|
@@ -2331,6 +2331,7 @@ Rebuilds the site, walks the output, and writes a markdown report to `audit/<YYY
|
|
|
2331
2331
|
| **Privacy** | External resource loads (URLs not on the site host) |
|
|
2332
2332
|
| **Dependencies** | Outdated gems via `bundle outdated`, CVEs via `bundle-audit` (if installed) |
|
|
2333
2333
|
| **Content** | Posts without descriptions, single-use tags, `series_position` problems that break prev/next silently (non-Integer, duplicate, or missing) |
|
|
2334
|
+
| **Security** | Inline code the built `_headers` CSP would block: `style="…"` attributes, `<style>` blocks, `on…=` handlers, and inline scripts whose hash isn't in `script-src`. Errors, because the page breaks on any host that enforces the policy |
|
|
2334
2335
|
|
|
2335
2336
|
The report opens with a summary table, a **Suggested fixes** code block listing every `bundle update <gem>` command needed to clear the dependency findings, and then groups findings by category with severity (`error`, `warn`, `info`) and a suggestion where applicable. Reports accumulate in `audit/` — useful to commit so you have a diffable history of site quality over time.
|
|
2336
2337
|
|
|
@@ -2365,7 +2366,7 @@ GitHub Dependabot does three things abqari's audit does **not**:
|
|
|
2365
2366
|
|
|
2366
2367
|
What `bin/audit` does that Dependabot doesn't:
|
|
2367
2368
|
|
|
2368
|
-
- **Same report covers links, SEO, accessibility, privacy, and
|
|
2369
|
+
- **Same report covers links, SEO, accessibility, privacy, content, and the CSP.** Dependabot only watches dependencies.
|
|
2369
2370
|
- **Uses the same `ruby-advisory-db`** Dependabot's Ruby ecosystem coverage uses, just at a single point in time.
|
|
2370
2371
|
- **Outputs to git-trackable markdown.** A diffable `audit/` history of every check you've run.
|
|
2371
2372
|
|
|
@@ -2558,14 +2559,14 @@ bin/audit # verify nothing regressed
|
|
|
2558
2559
|
```
|
|
2559
2560
|
|
|
2560
2561
|
`abqari new` scaffolds the Gemfile pinned to the current major.minor
|
|
2561
|
-
(`gem 'abqari', '~> 1.
|
|
2562
|
-
`>= 1.
|
|
2562
|
+
(`gem 'abqari', '~> 1.3'` as of this release). Bundler reads that as
|
|
2563
|
+
`>= 1.3, < 2.0`, so `bundle update abqari` takes minor and patch
|
|
2563
2564
|
releases but never a major one, which is the release that can need
|
|
2564
2565
|
changes to your site. To track a fork or an unreleased branch
|
|
2565
2566
|
instead, swap the line for a git source:
|
|
2566
2567
|
|
|
2567
2568
|
```ruby
|
|
2568
|
-
gem 'abqari', git: 'https://github.com/grantrayner/abqari.git', tag: 'v1.
|
|
2569
|
+
gem 'abqari', git: 'https://github.com/grantrayner/abqari.git', tag: 'v1.3.0'
|
|
2569
2570
|
```
|
|
2570
2571
|
|
|
2571
2572
|
The cadence: read [CHANGELOG.md](https://github.com/grantrayner/abqari/blob/main/CHANGELOG.md) before each upgrade,
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
require 'cgi'
|
|
5
|
+
require 'digest'
|
|
6
|
+
|
|
7
|
+
module Abqari
|
|
8
|
+
class Audit
|
|
9
|
+
# CSP rule. Checks every page's inline code against the
|
|
10
|
+
# Content-Security-Policy the same build wrote to `_headers`: inline
|
|
11
|
+
# `<script>` and `<style>` blocks, `style="…"` attributes and `on…=`
|
|
12
|
+
# event handlers.
|
|
13
|
+
#
|
|
14
|
+
# The policy is strict on purpose (`style-src 'self'`, scripts pinned
|
|
15
|
+
# by hash), so anything a template adds inline is blocked wherever
|
|
16
|
+
# the CSP is enforced — and nothing else notices. The dev server
|
|
17
|
+
# doesn't apply `_headers`, the page still renders, and the browser
|
|
18
|
+
# only logs a console error. One site carried 52 inline
|
|
19
|
+
# `style="--ratio: …"` attributes that would have collapsed every
|
|
20
|
+
# photo pane, and the engine's own speculation-rules block shipped in
|
|
21
|
+
# 1.1.x allowed only by a keyword Chrome ignores.
|
|
22
|
+
#
|
|
23
|
+
# Reads the BUILT `_headers`, not the engine's defaults, so a
|
|
24
|
+
# `headers:` override or `csp:` extension in site.yml is what gets
|
|
25
|
+
# checked. Error-severity: on a host that enforces the policy the
|
|
26
|
+
# page is broken. A site on a host that ignores `_headers` (GitHub
|
|
27
|
+
# Pages) will see these too; the code is still blocked the day the
|
|
28
|
+
# policy is applied.
|
|
29
|
+
class Csp < Rule
|
|
30
|
+
# Script types the browser executes, so CSP governs them. Any other
|
|
31
|
+
# `type` is a data block (JSON-LD, templates) and CSP ignores it.
|
|
32
|
+
EXECUTABLE_TYPES = %w[
|
|
33
|
+
text/javascript application/javascript application/ecmascript text/ecmascript
|
|
34
|
+
module importmap speculationrules
|
|
35
|
+
].freeze
|
|
36
|
+
HASH_ALGORITHMS = { 'sha256' => Digest::SHA256, 'sha384' => Digest::SHA384, 'sha512' => Digest::SHA512 }.freeze
|
|
37
|
+
|
|
38
|
+
# Most specific directive first, as browsers resolve them.
|
|
39
|
+
DIRECTIVES = {
|
|
40
|
+
script: %w[script-src-elem script-src default-src],
|
|
41
|
+
script_attr: %w[script-src-attr script-src default-src],
|
|
42
|
+
style: %w[style-src-elem style-src default-src],
|
|
43
|
+
style_attr: %w[style-src-attr style-src default-src]
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
LABELS = {
|
|
47
|
+
script: 'inline <script>',
|
|
48
|
+
script_attr: 'inline event handler',
|
|
49
|
+
style: '<style> block',
|
|
50
|
+
style_attr: 'style attribute'
|
|
51
|
+
}.freeze
|
|
52
|
+
|
|
53
|
+
ADVICE = {
|
|
54
|
+
script: 'Move it to a file under app/assets/js/, or add its sha256 to the CSP. ' \
|
|
55
|
+
'For the engine\'s own scripts, upgrade abqari and re-copy the CSP.',
|
|
56
|
+
script_attr: 'Attach the handler from a script file with addEventListener instead.',
|
|
57
|
+
style: 'Move the rules into app/assets/css/site.css.',
|
|
58
|
+
style_attr: 'Move the styles into app/assets/css/site.css as a class, or a data ' \
|
|
59
|
+
'attribute matched by an attribute selector for per-element values.'
|
|
60
|
+
}.freeze
|
|
61
|
+
|
|
62
|
+
def run
|
|
63
|
+
policies = csp_policies
|
|
64
|
+
return if policies.empty?
|
|
65
|
+
|
|
66
|
+
html_files.each do |file|
|
|
67
|
+
url = page_url(file)
|
|
68
|
+
applicable = policies.select { |prefix, _| url.start_with?(prefix) }.map(&:last)
|
|
69
|
+
next if applicable.empty?
|
|
70
|
+
|
|
71
|
+
check_page(url, File.read(file, encoding: 'UTF-8'), applicable)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# `[[path_prefix, { directive => [sources] }]]` for every CSP rule in
|
|
78
|
+
# the built `_headers`. More than one can apply to a page; the
|
|
79
|
+
# browser enforces all of them, so inline code has to pass each.
|
|
80
|
+
def csp_policies
|
|
81
|
+
path = File.join(@site.output_dir, '_headers')
|
|
82
|
+
return [] unless File.file?(path)
|
|
83
|
+
|
|
84
|
+
HeadersFile.parse(File.read(path, encoding: 'UTF-8'))
|
|
85
|
+
.select { |rule| rule['name'].casecmp?('Content-Security-Policy') }
|
|
86
|
+
.map { |rule| [rule['path'].delete_suffix('*'), parse_policy(rule['value'])] }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def parse_policy(value)
|
|
90
|
+
value.split(';').each_with_object({}) do |directive, policy|
|
|
91
|
+
name, *sources = directive.strip.split(/\s+/)
|
|
92
|
+
policy[name.downcase] = sources if name
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def check_page(url, html, policies)
|
|
97
|
+
blocked = Hash.new { |h, k| h[k] = [] }
|
|
98
|
+
inline_code(html).each do |kind, content, type|
|
|
99
|
+
policy = policies.find { |pol| !allowed?(pol, kind, content, type) }
|
|
100
|
+
blocked[kind] << [content, policy] if policy
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
blocked.each do |kind, hits|
|
|
104
|
+
content, policy = hits.first
|
|
105
|
+
directive = DIRECTIVES[kind].find { |d| policy.key?(d) }
|
|
106
|
+
count = hits.size
|
|
107
|
+
add(category: 'security', severity: 'error', location: url,
|
|
108
|
+
message: "#{count} #{LABELS[kind]}#{'s' unless count == 1} blocked by the CSP " \
|
|
109
|
+
"(#{directive} #{policy[directive].join(' ')}), e.g. #{example(kind, content)}",
|
|
110
|
+
suggestion: ADVICE[kind])
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Each piece of inline code as `[kind, content, script_type]`, where
|
|
115
|
+
# content is exactly what the browser hashes.
|
|
116
|
+
def inline_code(html)
|
|
117
|
+
html = html.gsub(/<!--.*?-->/m, '')
|
|
118
|
+
code = []
|
|
119
|
+
|
|
120
|
+
html.scan(%r{<script\b([^>]*)>(.*?)</script>}mi).each do |attrs, body|
|
|
121
|
+
next if attribute(attrs, 'src')
|
|
122
|
+
|
|
123
|
+
type = attribute(attrs, 'type')&.strip&.downcase
|
|
124
|
+
next unless type.nil? || type.empty? || EXECUTABLE_TYPES.include?(type)
|
|
125
|
+
|
|
126
|
+
code << [:script, body, type]
|
|
127
|
+
end
|
|
128
|
+
html.scan(%r{<style\b[^>]*>(.*?)</style>}mi).each { |(body)| code << [:style, body] }
|
|
129
|
+
|
|
130
|
+
# Attributes, with script and style bodies removed first so a
|
|
131
|
+
# string inside a script that looks like markup isn't counted.
|
|
132
|
+
tags_only = html.gsub(%r{<(script|style)\b[^>]*>.*?</\1>}mi, '')
|
|
133
|
+
tags_only.scan(/<[a-zA-Z][^>]*>/).each do |tag|
|
|
134
|
+
tag.scan(/\s(on[a-z]+|style)\s*=\s*(?:"([^"]*)"|'([^']*)')/i).each do |name, dq, sq|
|
|
135
|
+
code << [name.casecmp?('style') ? :style_attr : :script_attr, CGI.unescapeHTML(dq || sq)]
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
code
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def attribute(attrs, name)
|
|
142
|
+
match = attrs.match(/\b#{name}\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i)
|
|
143
|
+
match && (match[1] || match[2] || match[3])
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Mirrors the CSP spec's inline checks: a hash or nonce in the list
|
|
147
|
+
# switches `'unsafe-inline'` off; attribute hashes count only with
|
|
148
|
+
# `'unsafe-hashes'`. `'inline-speculation-rules'` is honoured only
|
|
149
|
+
# when the list has no hash or nonce: Chrome reports a block allowed
|
|
150
|
+
# by nothing but the keyword as a violation once hashes are present.
|
|
151
|
+
def allowed?(policy, kind, content, type = nil)
|
|
152
|
+
directive = DIRECTIVES[kind].find { |d| policy.key?(d) }
|
|
153
|
+
return true unless directive
|
|
154
|
+
|
|
155
|
+
sources = policy[directive]
|
|
156
|
+
hashed = sources.any? { |s| s.match?(/\A'(sha256|sha384|sha512|nonce)-/i) }
|
|
157
|
+
hashed ||= kind == :script && sources.include?("'strict-dynamic'")
|
|
158
|
+
return true if sources.include?("'unsafe-inline'") && !hashed
|
|
159
|
+
return true if type == 'speculationrules' && sources.include?("'inline-speculation-rules'") && !hashed
|
|
160
|
+
return false if %i[style_attr script_attr].include?(kind) && !sources.include?("'unsafe-hashes'")
|
|
161
|
+
|
|
162
|
+
HASH_ALGORITHMS.any? do |alg, digest|
|
|
163
|
+
sources.include?("'#{alg}-#{Base64.strict_encode64(digest.digest(content))}'")
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def example(kind, content)
|
|
168
|
+
snippet = content.strip.gsub(/\s+/, ' ')
|
|
169
|
+
snippet = "#{snippet[0, 60]}…" if snippet.length > 60
|
|
170
|
+
case kind
|
|
171
|
+
when :style_attr then %(style="#{snippet}")
|
|
172
|
+
when :script_attr then %(handler "#{snippet}")
|
|
173
|
+
else %(`#{snippet}`)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
data/lib/abqari/audit.rb
CHANGED
|
@@ -12,6 +12,7 @@ require_relative 'audit/privacy'
|
|
|
12
12
|
require_relative 'audit/performance'
|
|
13
13
|
require_relative 'audit/dependencies'
|
|
14
14
|
require_relative 'audit/content'
|
|
15
|
+
require_relative 'audit/csp'
|
|
15
16
|
|
|
16
17
|
module Abqari
|
|
17
18
|
# Site audit orchestrator. Each check lives in its own rule class
|
|
@@ -36,14 +37,15 @@ module Abqari
|
|
|
36
37
|
'privacy' => 'Privacy',
|
|
37
38
|
'perf' => 'Performance',
|
|
38
39
|
'deps' => 'Dependencies',
|
|
39
|
-
'content' => 'Content'
|
|
40
|
+
'content' => 'Content',
|
|
41
|
+
'security' => 'Security'
|
|
40
42
|
}.freeze
|
|
41
43
|
|
|
42
44
|
# Rule classes, in execution order. Order matters only for the
|
|
43
45
|
# output report — findings are grouped by category, not insertion
|
|
44
46
|
# order — but Links first keeps the report scan-friendly: errors
|
|
45
47
|
# appear before warnings.
|
|
46
|
-
RULES = [Links, SEO, A11y, Privacy, Performance, Dependencies, Content].freeze
|
|
48
|
+
RULES = [Links, SEO, A11y, Privacy, Performance, Dependencies, Content, Csp].freeze
|
|
47
49
|
|
|
48
50
|
attr_reader :findings
|
|
49
51
|
|
data/lib/abqari/cli/headers.rb
CHANGED
|
@@ -47,21 +47,8 @@ module Abqari
|
|
|
47
47
|
true
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
# `_headers` blocks: an unindented path line, then indented
|
|
51
|
-
# `Name: value` lines. Same shape HeadersFile writes, for both the
|
|
52
|
-
# generated policy and a `headers:` override in site.yml.
|
|
53
50
|
def parse(text)
|
|
54
|
-
|
|
55
|
-
text.each_line.with_object([]) do |line, rules|
|
|
56
|
-
next if line.strip.empty? || line.lstrip.start_with?('#')
|
|
57
|
-
|
|
58
|
-
if line.match?(/\A\S/)
|
|
59
|
-
path = line.strip
|
|
60
|
-
elsif path
|
|
61
|
-
name, value = line.strip.split(/:\s*/, 2)
|
|
62
|
-
rules << { 'path' => path, 'name' => name, 'value' => value.to_s } unless name.to_s.empty?
|
|
63
|
-
end
|
|
64
|
-
end
|
|
51
|
+
HeadersFile.parse(text)
|
|
65
52
|
end
|
|
66
53
|
|
|
67
54
|
private
|
data/lib/abqari/headers_file.rb
CHANGED
|
@@ -31,6 +31,24 @@ module Abqari
|
|
|
31
31
|
'Strict-Transport-Security' => 'max-age=31536000; includeSubDomains'
|
|
32
32
|
}.freeze
|
|
33
33
|
|
|
34
|
+
# A `_headers` file as `{ 'path', 'name', 'value' }` rules: an
|
|
35
|
+
# unindented path line, then indented `Name: value` lines. The shape
|
|
36
|
+
# `build` writes, for both the generated policy and a `headers:`
|
|
37
|
+
# override. Read back by `abqari headers` and the CSP audit.
|
|
38
|
+
def self.parse(text)
|
|
39
|
+
path = nil
|
|
40
|
+
text.each_line.with_object([]) do |line, rules|
|
|
41
|
+
next if line.strip.empty? || line.lstrip.start_with?('#')
|
|
42
|
+
|
|
43
|
+
if line.match?(/\A\S/)
|
|
44
|
+
path = line.strip
|
|
45
|
+
elsif path
|
|
46
|
+
name, value = line.strip.split(/:\s*/, 2)
|
|
47
|
+
rules << { 'path' => path, 'name' => name, 'value' => value.to_s } unless name.to_s.empty?
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
34
52
|
def initialize(site)
|
|
35
53
|
@site = site
|
|
36
54
|
end
|
data/lib/abqari/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: abqari
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Grant Rayner
|
|
@@ -220,6 +220,7 @@ files:
|
|
|
220
220
|
- lib/abqari/audit.rb
|
|
221
221
|
- lib/abqari/audit/a11y.rb
|
|
222
222
|
- lib/abqari/audit/content.rb
|
|
223
|
+
- lib/abqari/audit/csp.rb
|
|
223
224
|
- lib/abqari/audit/dependencies.rb
|
|
224
225
|
- lib/abqari/audit/links.rb
|
|
225
226
|
- lib/abqari/audit/performance.rb
|