fiber_audit 0.2.1 → 0.3.1
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/.fiber-audit.example.yml +7 -8
- data/ARCHITECTURE.md +97 -673
- data/CHANGELOG.md +52 -0
- data/README.md +163 -86
- data/lib/fiber_audit/cli.rb +19 -5
- data/lib/fiber_audit/configuration.rb +31 -6
- data/lib/fiber_audit/operation_semantics.rb +172 -0
- data/lib/fiber_audit/operation_vocabulary.rb +2 -1
- data/lib/fiber_audit/runtime/active_operations.rb +39 -9
- data/lib/fiber_audit/runtime/boot.rb +4 -0
- data/lib/fiber_audit/runtime/environment.rb +58 -0
- data/lib/fiber_audit/runtime/execution_context.rb +55 -43
- data/lib/fiber_audit/runtime/lifecycle.rb +91 -50
- data/lib/fiber_audit/runtime/operation_liveness_monitor.rb +282 -0
- data/lib/fiber_audit/runtime/operation_liveness_policy.rb +47 -0
- data/lib/fiber_audit/runtime/probes/base.rb +27 -4
- data/lib/fiber_audit/runtime/probes/subprocess.rb +89 -8
- data/lib/fiber_audit/runtime/probes/thread_state.rb +0 -14
- data/lib/fiber_audit/runtime/scheduler_evidence_classifier.rb +60 -0
- data/lib/fiber_audit/runtime/scheduler_observer.rb +54 -18
- data/lib/fiber_audit/runtime/scheduler_snapshot.rb +95 -0
- data/lib/fiber_audit/runtime/watchdog.rb +54 -4
- data/lib/fiber_audit/runtime.rb +5 -0
- data/lib/fiber_audit/static/call_site_extractor.rb +1 -0
- data/lib/fiber_audit/static/rules/base.rb +19 -0
- data/lib/fiber_audit/static/rules/blocking_subprocess.rb +67 -10
- data/lib/fiber_audit/static/rules/direct_socket.rb +54 -19
- data/lib/fiber_audit/static/rules/io_select.rb +6 -6
- data/lib/fiber_audit/static/rules/net_http_in_request.rb +11 -8
- data/lib/fiber_audit/static/rules/synchronization.rb +13 -8
- data/lib/fiber_audit/static/rules/thread_current_state.rb +20 -30
- data/lib/fiber_audit/static/rules/thread_join.rb +9 -7
- data/lib/fiber_audit/version.rb +1 -1
- metadata +6 -1
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,58 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.3.1 (2026-08-23)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added a lifecycle-owned operation-liveness monitor with strict policy transport,
|
|
10
|
+
explicit active/disabled/unsupported states, and bounded long-active
|
|
11
|
+
start/completion evidence independent of scheduler-heartbeat stalls.
|
|
12
|
+
- Added shared operation semantics and five Boolean/nil scheduler-classification
|
|
13
|
+
measurements to targeted operations, watchdog overlaps, and long-active events.
|
|
14
|
+
- Expanded the bounded scheduler semantic matrix for Process wait variants,
|
|
15
|
+
coordination hooks, localhost address resolution, and Ruby 4 IO-close
|
|
16
|
+
interruption; added a local measurement-only runtime probe benchmark.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Scheduler snapshot failures now remain `nil`/unknown in JSONL schema 1.0 rather
|
|
21
|
+
than asserting false state.
|
|
22
|
+
- FA1006 keeps rule/operation identity while distinguishing socket allocation,
|
|
23
|
+
endpoint setup, local connection, and unknown subclass constructor semantics.
|
|
24
|
+
- FA1004 now reports medium severity without execution-context escalation and
|
|
25
|
+
describes possible shared-state exposure without claiming request leakage.
|
|
26
|
+
- Documented the tested support contract as CRuby 3.3, 3.4, and 4.0 on Ubuntu
|
|
27
|
+
Linux; the gem installation requirement remains Ruby 3.3 or newer.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Corrected scheduler snapshots to preserve the actual `Fiber#blocking?` Boolean
|
|
32
|
+
instead of converting both blocking and non-blocking Fibers to true.
|
|
33
|
+
|
|
34
|
+
## 0.3.0 (2026-08-12)
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Reframed static rules as scheduler-cooperation requirements. Advisory rules no
|
|
39
|
+
longer escalate solely because they appear in request-like execution contexts.
|
|
40
|
+
- Split FA1001 under its existing rule ID into subprocess creation, replacement,
|
|
41
|
+
waiting, detach, and stream lifecycle semantics; added Process `spawn`, `exec`,
|
|
42
|
+
`wait`, `wait2`, `waitpid`, `waitpid2`, and `Process::Status.wait` coverage.
|
|
43
|
+
- Corrected FA1004 to report only true Thread variables. `Thread.current[]` and
|
|
44
|
+
`Thread.current[]=` are Fiber-local and are no longer static or runtime findings.
|
|
45
|
+
- Propagated Rails/runtime execution context into child Fibers through immutable
|
|
46
|
+
Ruby Fiber storage, with explicit current-Fiber `clear!`/`reset!` behavior.
|
|
47
|
+
- Added scheduler presence, blocking-Fiber state, and optional scheduler-hook
|
|
48
|
+
capability measurements to targeted operation events.
|
|
49
|
+
- Added bounded `scheduler_stall_operation_overlap` JSONL 1.0 events. They record
|
|
50
|
+
temporal overlap between a watchdog stall and active operations, not causality.
|
|
51
|
+
- Reconciled scheduler observer state only after `Fiber.set_scheduler` accepts a
|
|
52
|
+
change, so rejected replacement attempts preserve the previous observation.
|
|
53
|
+
- Added Ruby 4.0 to the required CI matrix and executable local scheduler-semantic
|
|
54
|
+
reproductions under `script/scheduler-semantics`, including Ruby 4's
|
|
55
|
+
`fiber_interrupt` scheduler protocol hook.
|
|
56
|
+
|
|
5
57
|
## 0.2.1 (2026-08-12)
|
|
6
58
|
|
|
7
59
|
### Fixed
|
data/README.md
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
# FiberAudit
|
|
2
2
|
|
|
3
|
-
FiberAudit audits Ruby and Rails code for operations that
|
|
4
|
-
|
|
5
|
-
observational runtime
|
|
3
|
+
FiberAudit audits Ruby and Rails code for operations that require cooperation
|
|
4
|
+
from a Fiber scheduler. It provides Rubydex/Prism-backed static hypotheses and
|
|
5
|
+
an explicitly activated observational runtime recorder.
|
|
6
6
|
|
|
7
7
|
> **Safety disclaimer:** FiberAudit does not prove that an application is
|
|
8
|
-
> fiber-safe.
|
|
9
|
-
>
|
|
8
|
+
> fiber-safe. Runtime observations prove execution, not scheduler harm; temporal
|
|
9
|
+
> overlap and long-active duration do not prove causality or deadlock. Absence of
|
|
10
|
+
> events does not establish execution coverage. FiberAudit never emits an
|
|
11
|
+
> unconditional `PASS`.
|
|
10
12
|
|
|
11
13
|
## Requirements and installation
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
The gem requires Ruby 3.3 or newer. CI tests CRuby 3.3, 3.4, and 4.0 on Ubuntu
|
|
16
|
+
Linux. Other Ruby engines and operating systems are not currently part of the
|
|
17
|
+
tested support contract. Native Rubydex packages must be available for the
|
|
18
|
+
selected platform.
|
|
14
19
|
|
|
15
20
|
```sh
|
|
16
21
|
gem install fiber_audit
|
|
17
22
|
```
|
|
18
23
|
|
|
19
|
-
Or add it to a bundle
|
|
24
|
+
Or add it to a bundle:
|
|
20
25
|
|
|
21
26
|
```ruby
|
|
22
27
|
gem "fiber_audit", require: false
|
|
@@ -24,15 +29,15 @@ gem "fiber_audit", require: false
|
|
|
24
29
|
|
|
25
30
|
## Quick start
|
|
26
31
|
|
|
27
|
-
Run from a project directory or
|
|
32
|
+
Run from a project directory or beneath it:
|
|
28
33
|
|
|
29
34
|
```sh
|
|
30
35
|
fiber-audit static
|
|
36
|
+
fiber-audit runtime -- bundle exec rspec
|
|
31
37
|
```
|
|
32
38
|
|
|
33
|
-
FiberAudit walks upward to
|
|
34
|
-
`config/application.rb
|
|
35
|
-
file exists.
|
|
39
|
+
FiberAudit walks upward to the nearest `Gemfile`, `gems.rb`, or
|
|
40
|
+
`config/application.rb` and loads `.fiber-audit.yml` from that root when present.
|
|
36
41
|
|
|
37
42
|
```text
|
|
38
43
|
fiber-audit static [--format text|json] [--config PATH] [--out PATH]
|
|
@@ -44,82 +49,135 @@ fiber-audit explain FA1001
|
|
|
44
49
|
fiber-audit version
|
|
45
50
|
```
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
JSON, writes only the report to that file, and prints a one-line
|
|
49
|
-
Explicit `--format` always wins.
|
|
52
|
+
Static output defaults to text on a TTY and JSON when piped. `--out PATH`
|
|
53
|
+
defaults to JSON, writes only the report to that file, and prints a one-line
|
|
54
|
+
confirmation. Explicit `--format` always wins.
|
|
50
55
|
|
|
51
56
|
## Shipped rules
|
|
52
57
|
|
|
53
58
|
| ID | Detects | Default severity |
|
|
54
59
|
|---|---|---|
|
|
55
|
-
| FA1001 |
|
|
56
|
-
| FA1002 |
|
|
57
|
-
| FA1003 |
|
|
58
|
-
| FA1004 | Thread-
|
|
59
|
-
| FA1005 |
|
|
60
|
-
| FA1006 |
|
|
61
|
-
| FA1007 |
|
|
60
|
+
| FA1001 | Subprocess creation, replacement, waiting, and streams | info/medium |
|
|
61
|
+
| FA1002 | Thread-wait scheduler coordination | low |
|
|
62
|
+
| FA1003 | Synchronization scheduler coordination | low/info |
|
|
63
|
+
| FA1004 | Thread-variable access shared across Fibers on one Thread | medium |
|
|
64
|
+
| FA1005 | `IO.select` scheduler capability requirement | medium |
|
|
65
|
+
| FA1006 | Socket allocation and constructor endpoint semantics | low |
|
|
66
|
+
| FA1007 | HTTP scheduler cooperation in request-like contexts | medium |
|
|
67
|
+
|
|
68
|
+
FA1004 reports true `thread_variable_get/set` access. It does not retain keys or
|
|
69
|
+
values and does not claim that request-sensitive leakage occurred. FA1006 keeps
|
|
70
|
+
stable `<Class>.new` operation identities while distinguishing inventory-only
|
|
71
|
+
allocation, address resolution/network endpoint setup, local connection, and
|
|
72
|
+
unknown IPSocket-subclass construction.
|
|
62
73
|
|
|
63
74
|
Use `fiber-audit explain <RULE_ID>` for exact targets and remediation.
|
|
64
75
|
|
|
65
76
|
## Runtime audit
|
|
66
77
|
|
|
67
|
-
The
|
|
68
|
-
never executes source fragments
|
|
78
|
+
The runtime command observes only the command supplied after `--`; static
|
|
79
|
+
analysis never executes discovered source fragments. Each observed Ruby process
|
|
80
|
+
writes a separate owner-only JSONL schema `1.0` session under
|
|
81
|
+
`tmp/fiber-audit-runtime` by default.
|
|
69
82
|
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
Targeted probes cover the operations represented by FA1001–FA1007. Events retain
|
|
84
|
+
canonical operation names, monotonic duration, conservative project-relative
|
|
85
|
+
callsites, execution context, ephemeral Thread/Fiber identities, and allowlisted
|
|
86
|
+
scalar measurements. They never retain commands, command arguments, URLs,
|
|
87
|
+
addresses, hosts, ports, headers, payloads, responses, return values, exception
|
|
88
|
+
messages, environment secrets, or thread-variable keys and values.
|
|
89
|
+
|
|
90
|
+
Libraries such as Open3, Monitor, Socket, Net::HTTP, and OpenURI may load after
|
|
91
|
+
runtime boot; FiberAudit rescans only known targets after `require`. Runtime
|
|
92
|
+
wrappers remain inert outside explicit activation and after deactivation or fork.
|
|
93
|
+
|
|
94
|
+
### Scheduler snapshots and operation classification
|
|
95
|
+
|
|
96
|
+
A targeted operation snapshots scheduler state at its start. Measurement failure
|
|
97
|
+
is represented as `nil` (unknown), never coerced to false:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
scheduler_present: true | false | nil
|
|
101
|
+
fiber_blocking: true | false | nil
|
|
102
|
+
scheduler_io_select_supported: true | false | nil
|
|
103
|
+
scheduler_process_wait_supported: true | false | nil
|
|
104
|
+
scheduler_address_resolve_supported: true | false | nil
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Operation-specific evidence adds five Boolean/nil measurements:
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
operation_wait_possible
|
|
111
|
+
operation_inventory_only
|
|
112
|
+
operation_scheduler_capability_required
|
|
113
|
+
operation_scheduler_capability_supported
|
|
114
|
+
operation_scheduler_cooperation_available
|
|
72
115
|
```
|
|
73
116
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
fiber-local context stack tracks the current execution context during probe
|
|
87
|
-
observations. Rails boundaries are wrapped via prepend hooks that become inert
|
|
88
|
-
after deactivation or fork, preserving application semantics without interfering
|
|
89
|
-
with normal Rails operation. The integration supports late loading: hooks are
|
|
90
|
-
installed when Rails components become available, even after runtime boot.
|
|
91
|
-
Static/runtime correlation remains future work.
|
|
92
|
-
|
|
93
|
-
The scheduler watchdog records one bounded start/completion pair when its
|
|
94
|
-
scheduler-owned heartbeat stops progressing past the configured threshold.
|
|
95
|
-
Scheduler-friendly waits should continue heartbeats. A session also records an
|
|
96
|
-
explicit watchdog state:
|
|
117
|
+
`operation_scheduler_cooperation_available: true` means the captured scheduler
|
|
118
|
+
and Fiber mode were compatible and, for an optional capability, the captured
|
|
119
|
+
hook was supported. Required core coordination hooks such as `block` and
|
|
120
|
+
`kernel_sleep` are inferred from known scheduler presence rather than measured
|
|
121
|
+
separately. This does not prove that the operation cooperated or completed
|
|
122
|
+
without delay. `nil` remains unknown or not applicable.
|
|
123
|
+
|
|
124
|
+
### Scheduler watchdog
|
|
125
|
+
|
|
126
|
+
The watchdog records one bounded start/completion pair when a scheduler-owned
|
|
127
|
+
heartbeat stops progressing past its threshold. Scheduler-friendly waits should
|
|
128
|
+
continue heartbeats. State events are:
|
|
97
129
|
|
|
98
130
|
- `watchdog_active` — a heartbeat ran under an installed scheduler;
|
|
99
131
|
- `watchdog_absent` — no scheduler was observed;
|
|
100
|
-
- `watchdog_unsupported` —
|
|
132
|
+
- `watchdog_unsupported` — the scheduler could not safely host observation;
|
|
101
133
|
- `watchdog_disabled` — watchdog policy disabled observation.
|
|
102
134
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
135
|
+
Bounded `scheduler_stall_operation_overlap` events associate active operation
|
|
136
|
+
sequences with a stall on the same Thread. They establish temporal overlap, not
|
|
137
|
+
causality. Native work retaining Ruby's GVL can prevent the watchdog Thread from
|
|
138
|
+
running until the work returns.
|
|
139
|
+
|
|
140
|
+
### Long-active operation monitor
|
|
141
|
+
|
|
142
|
+
The independent operation-liveness monitor polls the bounded active-operation
|
|
143
|
+
registry. By default it polls every 100 ms and emits after an observed operation
|
|
144
|
+
remains active for strictly more than 1 second. All targeted operation types are
|
|
145
|
+
eligible, but entries beyond the snapshot bound can remain unobserved under
|
|
146
|
+
registry pressure; truncation is explicit and absence is not a coverage claim:
|
|
147
|
+
|
|
148
|
+
- `operation_liveness_active`;
|
|
149
|
+
- `operation_liveness_disabled`;
|
|
150
|
+
- `operation_liveness_unsupported`;
|
|
151
|
+
- `operation_long_active_started`;
|
|
152
|
+
- `operation_long_active_completed`.
|
|
153
|
+
|
|
154
|
+
Ordinary registry removal closes a pair with `operation_finished: true`.
|
|
155
|
+
Shutdown closes an open pair with `operation_finished: false`; that value does
|
|
156
|
+
not mean the application operation failed. Long-active evidence means only that
|
|
157
|
+
a targeted operation remained registered across the threshold. It is not a
|
|
158
|
+
scheduler stall, proven deadlock, or proof of scheduler harm.
|
|
159
|
+
|
|
160
|
+
Watchdog, liveness-state, overlap, and long-active events bypass random sampling
|
|
161
|
+
but still consume recorder rate, event, record-size, and session-size budgets.
|
|
162
|
+
Snapshot and per-poll truncation, drops, unsupported states, internal errors, and
|
|
163
|
+
incomplete sessions remain visible. A successful `exec` may intentionally leave
|
|
164
|
+
a session without `session_end`.
|
|
165
|
+
|
|
166
|
+
Rails execution contexts (`request`, `middleware`, `job`, and `websocket`) are
|
|
167
|
+
captured when Rails integration is active. Bounded immutable Fiber storage
|
|
168
|
+
propagates logical context to child Fibers without retaining request data.
|
|
169
|
+
Combined static/runtime reporting remains future work.
|
|
106
170
|
|
|
107
171
|
## Configuration
|
|
108
172
|
|
|
109
|
-
Copy `.fiber-audit.example.yml` to `.fiber-audit.yml
|
|
110
|
-
|
|
111
|
-
|
|
173
|
+
Copy `.fiber-audit.example.yml` to `.fiber-audit.yml`. Paths and globs are rooted
|
|
174
|
+
at the detected project; explicit `--config` is resolved from the invocation
|
|
175
|
+
directory.
|
|
112
176
|
|
|
113
177
|
```yaml
|
|
114
178
|
static:
|
|
115
|
-
include:
|
|
116
|
-
|
|
117
|
-
- lib/**/*.rb
|
|
118
|
-
- config/**/*.rb
|
|
119
|
-
exclude:
|
|
120
|
-
- vendor/**/*
|
|
121
|
-
- tmp/**/*
|
|
122
|
-
- db/schema.rb
|
|
179
|
+
include: [app/**/*.rb, lib/**/*.rb, config/**/*.rb]
|
|
180
|
+
exclude: [vendor/**/*, tmp/**/*, db/schema.rb]
|
|
123
181
|
suppressions_path: .fiber-audit-suppressions.yml
|
|
124
182
|
|
|
125
183
|
rules:
|
|
@@ -137,40 +195,45 @@ runtime:
|
|
|
137
195
|
mode: strict
|
|
138
196
|
sampling:
|
|
139
197
|
rate: 0.1
|
|
198
|
+
overhead:
|
|
199
|
+
max_events_per_second: 100
|
|
200
|
+
max_events_per_session: 10000
|
|
201
|
+
max_record_bytes: 16384
|
|
202
|
+
max_session_bytes: 10485760
|
|
140
203
|
watchdog:
|
|
141
204
|
enabled: true
|
|
142
205
|
heartbeat_interval_ms: 25
|
|
143
206
|
stall_threshold_ms: 100
|
|
144
207
|
max_frames: 20
|
|
208
|
+
operation_liveness:
|
|
209
|
+
enabled: true
|
|
210
|
+
poll_interval_ms: 100
|
|
211
|
+
long_active_threshold_ms: 1000
|
|
145
212
|
fail_open: true
|
|
146
213
|
```
|
|
147
214
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
215
|
+
Configuration is strict: unknown sections or keys, invalid types, and values
|
|
216
|
+
outside policy bounds return exit code 2. `--min-severity` overrides
|
|
217
|
+
`report.min_severity` for one static run. Severity ordering is `critical`,
|
|
218
|
+
`high`, `medium`, `low`, `info`; the default `low` threshold omits informational
|
|
219
|
+
findings.
|
|
152
220
|
|
|
153
221
|
## Suppressions
|
|
154
222
|
|
|
155
223
|
Every suppression requires a non-empty reason. Directive-looking text inside
|
|
156
224
|
strings, heredocs, or regular expressions is ignored.
|
|
157
225
|
|
|
158
|
-
Suppress one line:
|
|
159
|
-
|
|
160
226
|
```ruby
|
|
161
227
|
system(command) # fiber-audit:disable FA1001 -- trusted maintenance command
|
|
162
228
|
```
|
|
163
229
|
|
|
164
|
-
Suppress a block:
|
|
165
|
-
|
|
166
230
|
```ruby
|
|
167
231
|
# fiber-audit:disable FA1003 -- protected legacy boundary
|
|
168
232
|
mutex.synchronize { update_record }
|
|
169
233
|
# fiber-audit:enable FA1003
|
|
170
234
|
```
|
|
171
235
|
|
|
172
|
-
|
|
173
|
-
operation. Point `static.suppressions_path` at the file:
|
|
236
|
+
YAML suppressions can match rule and optionally symbol or operation:
|
|
174
237
|
|
|
175
238
|
```yaml
|
|
176
239
|
suppressions:
|
|
@@ -180,29 +243,43 @@ suppressions:
|
|
|
180
243
|
reason: isolated worker process with an external timeout
|
|
181
244
|
```
|
|
182
245
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
## Static statuses
|
|
246
|
+
## Static statuses and exit codes
|
|
186
247
|
|
|
187
248
|
- `FAIL` — at least one critical or high finding.
|
|
188
|
-
- `REVIEW` — a medium finding, or
|
|
189
|
-
finding.
|
|
249
|
+
- `REVIEW` — a medium finding, or non-informational low/unknown-confidence risk.
|
|
190
250
|
- `PASS_WITH_WARNINGS` — only low or informational findings.
|
|
191
251
|
- `NO_FINDINGS` — no findings at the configured threshold.
|
|
192
252
|
|
|
193
|
-
FiberAudit never emits unconditional `PASS
|
|
194
|
-
|
|
195
|
-
## Exit codes
|
|
253
|
+
FiberAudit never emits unconditional `PASS`.
|
|
196
254
|
|
|
197
255
|
| Code | Meaning |
|
|
198
256
|
|---|---|
|
|
199
257
|
| 0 | No active finding at or above the configured threshold |
|
|
200
258
|
| 1 | One or more active findings at or above the threshold |
|
|
201
259
|
| 2 | Invalid options, configuration, analysis, or report output |
|
|
202
|
-
| 3 | Reserved;
|
|
260
|
+
| 3 | Reserved; not emitted |
|
|
261
|
+
|
|
262
|
+
Source parse errors remain report data while analysis continues on other files.
|
|
263
|
+
|
|
264
|
+
## Development and semantic verification
|
|
265
|
+
|
|
266
|
+
```sh
|
|
267
|
+
bundle exec rspec
|
|
268
|
+
bundle exec rubocop
|
|
269
|
+
bundle exec ruby script/scheduler-semantics
|
|
270
|
+
FIBER_AUDIT_BENCH_ITERATIONS=2000 bundle exec ruby benchmark/runtime_probe_overhead.rb
|
|
271
|
+
gem build fiber_audit.gemspec
|
|
272
|
+
bundle exec rake release:sanity
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The semantic script uses bounded local Threads, pipes, localhost resolution, and
|
|
276
|
+
child processes. CI exercises it through the spec suite on tested CRuby versions.
|
|
277
|
+
It verifies only scheduler capabilities consumed by FiberAudit; it is not a full
|
|
278
|
+
scheduler-conformance suite.
|
|
203
279
|
|
|
204
|
-
|
|
205
|
-
|
|
280
|
+
The benchmark reports absent, installed/inactive, active sampling-zero, and
|
|
281
|
+
active sampling-one workloads in isolated subprocesses. It is diagnostic only:
|
|
282
|
+
there is no host-dependent CI timing threshold.
|
|
206
283
|
|
|
207
|
-
See [ARCHITECTURE.md](ARCHITECTURE.md) for implementation boundaries
|
|
208
|
-
|
|
284
|
+
See [ARCHITECTURE.md](ARCHITECTURE.md) for implementation boundaries and the
|
|
285
|
+
runtime truthfulness, privacy, lifecycle, and schema contracts.
|
data/lib/fiber_audit/cli.rb
CHANGED
|
@@ -101,6 +101,7 @@ module FiberAudit
|
|
|
101
101
|
environment = Runtime::Environment.child_environment(
|
|
102
102
|
settings: settings,
|
|
103
103
|
watchdog_policy: configuration.runtime_watchdog_policy,
|
|
104
|
+
operation_liveness_policy: configuration.runtime_operation_liveness_policy,
|
|
104
105
|
probes_enabled: true
|
|
105
106
|
)
|
|
106
107
|
Runtime::Supervisor.new(
|
|
@@ -202,7 +203,8 @@ module FiberAudit
|
|
|
202
203
|
min_severity: severity,
|
|
203
204
|
suppressions_path: configuration.suppressions_path,
|
|
204
205
|
runtime_policy: configuration.runtime_policy,
|
|
205
|
-
runtime_watchdog_policy: configuration.runtime_watchdog_policy
|
|
206
|
+
runtime_watchdog_policy: configuration.runtime_watchdog_policy,
|
|
207
|
+
runtime_operation_liveness_policy: configuration.runtime_operation_liveness_policy
|
|
206
208
|
)
|
|
207
209
|
end
|
|
208
210
|
|
|
@@ -274,12 +276,25 @@ module FiberAudit
|
|
|
274
276
|
stdout.puts "Description: #{rule_class.description}"
|
|
275
277
|
stdout.puts 'Targets:'
|
|
276
278
|
rule_targets(rule_class).each { |target| stdout.puts " - #{target}" }
|
|
277
|
-
stdout
|
|
279
|
+
print_rule_remediations(rule_class, stdout)
|
|
278
280
|
0
|
|
279
281
|
end
|
|
280
282
|
|
|
283
|
+
def print_rule_remediations(rule_class, stdout)
|
|
284
|
+
values = if rule_class.const_defined?(:REMEDIATION, false)
|
|
285
|
+
[rule_class.const_get(:REMEDIATION)]
|
|
286
|
+
elsif rule_class.const_defined?(:CATEGORY_METADATA, false)
|
|
287
|
+
rule_class.const_get(:CATEGORY_METADATA).values.map { |metadata| metadata.fetch(:remediation) }.uniq
|
|
288
|
+
else
|
|
289
|
+
[]
|
|
290
|
+
end
|
|
291
|
+
values.each_with_index do |remediation, index|
|
|
292
|
+
stdout.puts "#{index.zero? ? 'Remediation:' : ' '} #{remediation}"
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
281
296
|
def rule_title(rule_class)
|
|
282
|
-
return '
|
|
297
|
+
return 'Subprocess lifecycle operations' if rule_class.id == 'FA1001'
|
|
283
298
|
|
|
284
299
|
%i[TITLE RULE_TITLE].each do |name|
|
|
285
300
|
return rule_class.const_get(name) if rule_class.const_defined?(name, false)
|
|
@@ -296,8 +311,7 @@ module FiberAudit
|
|
|
296
311
|
when 'FA1003'
|
|
297
312
|
expand_target_map(rule_class::TARGETS, '#')
|
|
298
313
|
when 'FA1004'
|
|
299
|
-
rule_class::THREAD_VARIABLE_METHODS.map { |method| "Thread##{method}" }
|
|
300
|
-
rule_class::INDEX_METHODS.map { |method| "Thread.current.#{method}" }
|
|
314
|
+
rule_class::THREAD_VARIABLE_METHODS.map { |method| "Thread##{method}" }
|
|
301
315
|
when 'FA1005'
|
|
302
316
|
expand_target_map(rule_class::TARGETS.transform_values { |method| [method] }, '.')
|
|
303
317
|
when 'FA1006'
|
|
@@ -6,6 +6,7 @@ require_relative 'errors'
|
|
|
6
6
|
require_relative 'findings/severity'
|
|
7
7
|
require_relative 'runtime/policy'
|
|
8
8
|
require_relative 'runtime/watchdog_policy'
|
|
9
|
+
require_relative 'runtime/operation_liveness_policy'
|
|
9
10
|
|
|
10
11
|
module FiberAudit
|
|
11
12
|
# rubocop:disable Metrics/ClassLength
|
|
@@ -14,7 +15,8 @@ module FiberAudit
|
|
|
14
15
|
KNOWN_STATIC_KEYS = %w[include exclude suppressions_path].freeze
|
|
15
16
|
KNOWN_REPORT_KEYS = %w[formats min_severity].freeze
|
|
16
17
|
KNOWN_RULE_KEYS = %w[enabled severity].freeze
|
|
17
|
-
KNOWN_RUNTIME_KEYS = %w[redaction sampling overhead watchdog fail_open].freeze
|
|
18
|
+
KNOWN_RUNTIME_KEYS = %w[redaction sampling overhead watchdog operation_liveness fail_open].freeze
|
|
19
|
+
KNOWN_OPERATION_LIVENESS_KEYS = %w[enabled poll_interval_ms long_active_threshold_ms].freeze
|
|
18
20
|
KNOWN_REDACTION_KEYS = %w[mode].freeze
|
|
19
21
|
KNOWN_SAMPLING_KEYS = %w[rate].freeze
|
|
20
22
|
KNOWN_OVERHEAD_KEYS = %w[
|
|
@@ -50,7 +52,8 @@ module FiberAudit
|
|
|
50
52
|
|
|
51
53
|
attr_reader :static_include, :static_exclude, :rules_config,
|
|
52
54
|
:report_formats, :min_severity, :suppressions_path,
|
|
53
|
-
:runtime_policy, :runtime_watchdog_policy
|
|
55
|
+
:runtime_policy, :runtime_watchdog_policy,
|
|
56
|
+
:runtime_operation_liveness_policy
|
|
54
57
|
|
|
55
58
|
def initialize(
|
|
56
59
|
static_include: DEFAULT_STATIC_INCLUDE,
|
|
@@ -60,12 +63,13 @@ module FiberAudit
|
|
|
60
63
|
min_severity: :low,
|
|
61
64
|
suppressions_path: nil,
|
|
62
65
|
runtime_policy: Runtime::Policy.new,
|
|
63
|
-
runtime_watchdog_policy: Runtime::WatchdogPolicy.new
|
|
66
|
+
runtime_watchdog_policy: Runtime::WatchdogPolicy.new,
|
|
67
|
+
runtime_operation_liveness_policy: Runtime::OperationLivenessPolicy.new
|
|
64
68
|
)
|
|
65
69
|
validate_types!(
|
|
66
70
|
static_include, static_exclude, rules_config,
|
|
67
71
|
report_formats, min_severity, suppressions_path, runtime_policy,
|
|
68
|
-
runtime_watchdog_policy
|
|
72
|
+
runtime_watchdog_policy, runtime_operation_liveness_policy
|
|
69
73
|
)
|
|
70
74
|
|
|
71
75
|
@static_include = static_include
|
|
@@ -76,6 +80,7 @@ module FiberAudit
|
|
|
76
80
|
@suppressions_path = suppressions_path
|
|
77
81
|
@runtime_policy = runtime_policy
|
|
78
82
|
@runtime_watchdog_policy = runtime_watchdog_policy
|
|
83
|
+
@runtime_operation_liveness_policy = runtime_operation_liveness_policy
|
|
79
84
|
end
|
|
80
85
|
|
|
81
86
|
def rule_enabled?(rule_id)
|
|
@@ -113,7 +118,8 @@ module FiberAudit
|
|
|
113
118
|
min_severity: report.fetch('min_severity', :low),
|
|
114
119
|
suppressions_path: static['suppressions_path'],
|
|
115
120
|
runtime_policy: runtime_policy_from(runtime),
|
|
116
|
-
runtime_watchdog_policy: runtime_watchdog_policy_from(runtime)
|
|
121
|
+
runtime_watchdog_policy: runtime_watchdog_policy_from(runtime),
|
|
122
|
+
runtime_operation_liveness_policy: runtime_operation_liveness_policy_from(runtime)
|
|
117
123
|
)
|
|
118
124
|
end
|
|
119
125
|
|
|
@@ -166,6 +172,7 @@ module FiberAudit
|
|
|
166
172
|
validate_runtime_mapping!(runtime, 'sampling', KNOWN_SAMPLING_KEYS)
|
|
167
173
|
validate_runtime_mapping!(runtime, 'overhead', KNOWN_OVERHEAD_KEYS)
|
|
168
174
|
validate_runtime_mapping!(runtime, 'watchdog', KNOWN_WATCHDOG_KEYS)
|
|
175
|
+
validate_runtime_mapping!(runtime, 'operation_liveness', KNOWN_OPERATION_LIVENESS_KEYS)
|
|
169
176
|
end
|
|
170
177
|
|
|
171
178
|
def validate_runtime_mapping!(runtime, key, allowed)
|
|
@@ -214,6 +221,19 @@ module FiberAudit
|
|
|
214
221
|
raise ConfigurationError, "#{path} is invalid: #{e.message}"
|
|
215
222
|
end
|
|
216
223
|
|
|
224
|
+
def runtime_operation_liveness_policy_from(runtime)
|
|
225
|
+
values = runtime.fetch('operation_liveness', {})
|
|
226
|
+
defaults = Runtime::OperationLivenessPolicy::DEFAULTS
|
|
227
|
+
Runtime::OperationLivenessPolicy.new(
|
|
228
|
+
enabled: values.fetch('enabled', defaults[:enabled]),
|
|
229
|
+
poll_interval_ms: values.fetch('poll_interval_ms', defaults[:poll_interval_ms]),
|
|
230
|
+
long_active_threshold_ms: values.fetch('long_active_threshold_ms', defaults[:long_active_threshold_ms])
|
|
231
|
+
)
|
|
232
|
+
rescue RuntimeContractError => e
|
|
233
|
+
field = e.message.split.first
|
|
234
|
+
raise ConfigurationError, "runtime.operation_liveness.#{field} is invalid: #{e.message}"
|
|
235
|
+
end
|
|
236
|
+
|
|
217
237
|
def check_unknown_keys(hash, allowed, path)
|
|
218
238
|
unknown = hash.keys - allowed
|
|
219
239
|
return if unknown.empty?
|
|
@@ -229,7 +249,8 @@ module FiberAudit
|
|
|
229
249
|
|
|
230
250
|
def validate_types!(
|
|
231
251
|
include_patterns, exclude_patterns, rules,
|
|
232
|
-
formats, _severity, suppressions, runtime_policy, watchdog_policy
|
|
252
|
+
formats, _severity, suppressions, runtime_policy, watchdog_policy,
|
|
253
|
+
operation_liveness_policy
|
|
233
254
|
)
|
|
234
255
|
unless include_patterns.is_a?(Array) &&
|
|
235
256
|
include_patterns.all?(String)
|
|
@@ -256,6 +277,10 @@ module FiberAudit
|
|
|
256
277
|
raise ConfigurationError,
|
|
257
278
|
'runtime_watchdog_policy must be a FiberAudit::Runtime::WatchdogPolicy'
|
|
258
279
|
end
|
|
280
|
+
unless operation_liveness_policy.is_a?(Runtime::OperationLivenessPolicy)
|
|
281
|
+
raise ConfigurationError,
|
|
282
|
+
'runtime_operation_liveness_policy must be a FiberAudit::Runtime::OperationLivenessPolicy'
|
|
283
|
+
end
|
|
259
284
|
|
|
260
285
|
return if suppressions.nil? || suppressions.is_a?(String)
|
|
261
286
|
|