Extract context patterns from webhook schemata (#745)

This commit is contained in:
William Woodruff 2025-05-22 15:31:05 -04:00 committed by GitHub
parent 855aa0b183
commit 46f61576d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 5496 additions and 386 deletions

83
.github/workflows/codegen.yml vendored Normal file
View file

@ -0,0 +1,83 @@
name: Code generation 🤖
on:
workflow_dispatch:
schedule:
- cron: '0 12 * * 1'
permissions: {}
env:
PR_ASSIGNEES: woodruffw
jobs:
refresh-schemas:
name: Refresh JSON schemas 📈
runs-on: ubuntu-latest
permissions:
contents: write # for creating branches
pull-requests: write # for opening PRs
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: try to refresh schemas
run: |
make refresh-schemas
- name: create PR
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
commit-message: "[BOT] update JSON schemas from SchemaStore"
branch: refresh-schemas
branch-suffix: timestamp
title: "[BOT] update JSON schemas from SchemaStore"
body: |
:robot: :warning: :robot:
This is an automated pull request, updating the embedded JSON
schemas after a SchemaStore change was detected.
Please review manually before merging.
assignees: ${{ env.PR_ASSIGNEES }}
reviewers: ${{ env.PR_ASSIGNEES }}
refresh-context-capabilities:
name: Refresh context capabilities *️⃣
runs-on: ubuntu-latest
permissions:
contents: write # for creating branches
pull-requests: write # for opening PRs
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1
- name: try to refresh context capabilities
run: |
make webhooks-to-contexts
- name: create PR
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
commit-message: "[BOT] update context capabilities"
branch: refresh-context-capabilities
branch-suffix: timestamp
title: "[BOT] update context-capabilities from GitHub webhooks"
body: |
:robot: :warning: :robot:
This is an automated pull request, updating the
context capabilities CSV after a change to GitHub's
webhooks was detected.
Please review manually before merging.
assignees: ${{ env.PR_ASSIGNEES }}
reviewers: ${{ env.PR_ASSIGNEES }}

View file

@ -1,40 +0,0 @@
name: Refresh JSON schemas
on:
workflow_dispatch:
schedule:
- cron: '0 12 * * 1'
permissions: {}
jobs:
refresh-schemas:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: try to refresh schemas
run: |
make refresh-schemas
- name: create PR
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
commit-message: "[BOT] update JSON schemas from SchemaStore"
branch: refresh-schemas
branch-suffix: timestamp
title: "[BOT] update JSON schemas from SchemaStore"
body: |
This is an automated pull request, updating `src/data`
after a detected change in the JSON schemas from SchemaStore.
Please review manually before merging.
assignees: "woodruffw"
reviewers: "woodruffw"

29
Cargo.lock generated
View file

@ -454,6 +454,27 @@ dependencies = [
"typenum",
]
[[package]]
name = "csv"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
dependencies = [
"memchr",
]
[[package]]
name = "deranged"
version = "0.4.0"
@ -625,6 +646,12 @@ dependencies = [
"num",
]
[[package]]
name = "fst"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a"
[[package]]
name = "futures"
version = "0.3.31"
@ -3615,8 +3642,10 @@ dependencies = [
"clap-verbosity-flag",
"clap_complete",
"clap_complete_nushell",
"csv",
"etcetera",
"flate2",
"fst",
"github-actions-expressions",
"github-actions-models",
"http-cache-reqwest",

View file

@ -30,8 +30,10 @@ clap = "4.5.38"
clap-verbosity-flag = { version = "3.0.2", default-features = false }
clap_complete = "4.5.50"
clap_complete_nushell = "4.5.5"
csv = "1.3.1"
etcetera = "0.10.0"
flate2 = "1.1.1"
fst = "0.4.7"
http-cache-reqwest = "0.15.1"
human-panic = "2.0.1"
ignore = "0.4.23"

View file

@ -31,6 +31,10 @@ refresh-schemas:
curl https://json.schemastore.org/github-workflow.json > crates/zizmor/src/data/github-workflow.json
curl https://json.schemastore.org/github-action.json > crates/zizmor/src/data/github-action.json
.PHONY: webhooks-to-contexts
webhooks-to-contexts: support/known-safe-contexts.txt
uv run --script --only-group codegen ./support/webhooks-to-contexts.py
.PHONY: pinact
pinact:
pinact run --update --verify

View file

@ -1,5 +1,6 @@
//! Parsing and matching APIs for GitHub Actions expressions
//! contexts (e.g. `github.event.name`).
use super::Expr;
/// Represents a context in a GitHub Actions expression.
@ -47,6 +48,54 @@ impl<'src> Context<'src> {
_ => None,
}
}
/// Returns the "pattern equivalent" of this context.
///
/// This is a string that can be used to efficiently match the context,
/// such as is done in `zizmor`'s template-injection audit via a
/// finite state transducer.
///
/// Returns None if the context doesn't have a sensible pattern
/// equivalent, e.g. if it starts with a call.
pub fn as_pattern(&self) -> Option<String> {
fn push_part(part: &Expr<'_>, pattern: &mut String) {
match part {
Expr::Identifier(ident) => pattern.push_str(ident.0),
Expr::Star => pattern.push('*'),
Expr::Index(idx) => match idx.as_ref() {
// foo['bar'] -> foo.bar
Expr::String(idx) => pattern.push_str(idx),
// any kind of numeric or computed index, e.g.:
// foo[0], foo[1 + 2], foo[bar]
_ => pattern.push('*'),
},
_ => unreachable!("unexpected part in context pattern"),
}
}
// TODO: Optimization ideas:
// 1. Add a happy path for contexts that contain only
// identifiers? Problem: case normalization.
// 2. Use `regex-automata` to return a case insensitive
// automation here?
let mut pattern = String::with_capacity(self.raw.len());
let mut parts = self.parts.iter().peekable();
let head = parts.next()?;
if matches!(head, Expr::Call { .. }) {
return None;
}
push_part(head, &mut pattern);
for part in parts {
pattern.push('.');
push_part(part, &mut pattern);
}
pattern.make_ascii_lowercase();
Some(pattern)
}
}
impl PartialEq for Context<'_> {
@ -120,33 +169,28 @@ impl<'src> ContextPattern<'src> {
}
}
fn compare_part(pattern: &str, part: &Expr<'src>) -> bool {
if pattern == "*" {
true
} else {
match part {
Expr::Identifier(part) => pattern.eq_ignore_ascii_case(part.0),
Expr::Index(part) => match part.as_ref() {
Expr::String(part) => pattern.eq_ignore_ascii_case(part),
_ => false,
},
_ => false,
}
}
}
fn compare(&self, ctx: &Context<'src>) -> Option<Comparison> {
let mut pattern_parts = self.0.split('.').peekable();
let mut ctx_parts = ctx.parts.iter().peekable();
while let (Some(pattern), Some(part)) = (pattern_parts.peek(), ctx_parts.peek()) {
// TODO: Refactor this; it's way too hard to read.
match (*pattern, part) {
// Calls can't be compared to patterns.
(_, Expr::Call { .. }) => return None,
// "*" matches any part.
("*", _) => {}
(_, Expr::Star) => return None,
(pattern, Expr::Identifier(part)) if !pattern.eq_ignore_ascii_case(part.0) => {
return None;
}
(pattern, Expr::Index(idx)) => {
// Anything other than a string index is invalid
// for part-wise comparison.
let Expr::String(part) = idx.as_ref() else {
return None;
};
if !pattern.eq_ignore_ascii_case(part) {
return None;
}
}
_ => {}
if !Self::compare_part(pattern, part) {
return None;
}
pattern_parts.next();
@ -253,6 +297,45 @@ mod tests {
}
}
#[test]
fn test_context_as_pattern() {
for (case, expected) in &[
// Basic cases.
("foo", Some("foo")),
("foo.bar", Some("foo.bar")),
("foo.bar.baz", Some("foo.bar.baz")),
("foo.bar.baz_baz", Some("foo.bar.baz_baz")),
("foo.bar.baz-baz", Some("foo.bar.baz-baz")),
("foo.*", Some("foo.*")),
("foo.bar.*", Some("foo.bar.*")),
("foo.*.baz", Some("foo.*.baz")),
("foo.*.*", Some("foo.*.*")),
// Case sensitivity.
("FOO", Some("foo")),
("FOO.BAR", Some("foo.bar")),
("FOO.BAR.BAZ", Some("foo.bar.baz")),
("FOO.BAR.BAZ_BAZ", Some("foo.bar.baz_baz")),
("FOO.BAR.BAZ-BAZ", Some("foo.bar.baz-baz")),
("FOO.*", Some("foo.*")),
("FOO.BAR.*", Some("foo.bar.*")),
("FOO.*.BAZ", Some("foo.*.baz")),
("FOO.*.*", Some("foo.*.*")),
// Indexes.
("foo.bar.baz[0]", Some("foo.bar.baz.*")),
("foo.bar.baz['abc']", Some("foo.bar.baz.abc")),
("foo.bar.baz[0].qux", Some("foo.bar.baz.*.qux")),
("foo.bar.baz[0].qux[1]", Some("foo.bar.baz.*.qux.*")),
("foo[1][2][3]", Some("foo.*.*.*")),
("foo.bar[abc]", Some("foo.bar.*")),
("foo.bar[abc()]", Some("foo.bar.*")),
// Invalid cases
("foo().bar", None),
] {
let ctx = Context::try_from(*case).unwrap();
assert_eq!(ctx.as_pattern().as_deref(), *expected);
}
}
#[test]
fn test_contextpattern_new() {
for (case, expected) in &[

View file

@ -28,7 +28,7 @@ mod parser {
///
/// Function names are case-insensitive.
#[derive(Debug)]
pub struct Function<'src>(&'src str);
pub struct Function<'src>(pub(crate) &'src str);
impl PartialEq for Function<'_> {
fn eq(&self, other: &Self) -> bool {
@ -455,7 +455,7 @@ mod tests {
use pest::Parser as _;
use pretty_assertions::assert_eq;
use super::{BinOp, Context, Expr, ExprParser, Function, Rule, UnOp};
use super::{BinOp, Expr, ExprParser, Function, Rule, UnOp};
#[test]
fn test_function_eq() {
@ -467,58 +467,6 @@ mod tests {
assert_eq!(func, Function("FOO"));
}
#[test]
fn test_context_eq() {
let ctx = Context::try_from("foo.bar.baz").unwrap();
assert_eq!(&ctx, "foo.bar.baz");
assert_eq!(&ctx, "FOO.BAR.BAZ");
assert_eq!(&ctx, "Foo.Bar.Baz");
}
#[test]
fn test_context_child_of() {
let ctx = Context::try_from("foo.bar.baz").unwrap();
for (case, child) in &[
// Trivial child cases.
("foo", true),
("foo.bar", true),
// Case-insensitive cases.
("FOO", true),
("FOO.BAR", true),
("Foo", true),
("Foo.Bar", true),
// We consider a context to be a child of itself.
("foo.bar.baz", true),
// Trivial non-child cases.
("foo.bar.baz.qux", false),
("foo.bar.qux", false),
("foo.qux", false),
("qux", false),
// Invalid cases.
("foo.", false),
(".", false),
("", false),
] {
assert_eq!(ctx.child_of(*case), *child);
}
}
#[test]
fn test_context_pop_if() {
let ctx = Context::try_from("foo.bar.baz").unwrap();
for (case, expected) in &[
("foo", Some("bar.baz")),
("Foo", Some("bar.baz")),
("FOO", Some("bar.baz")),
("foo.", None),
("bar", None),
] {
assert_eq!(ctx.pop_if(case), *expected);
}
}
#[test]
fn test_parse_string_rule() {
let cases = &[

View file

@ -33,6 +33,7 @@ clap_complete.workspace = true
clap_complete_nushell.workspace = true
etcetera.workspace = true
flate2.workspace = true
fst.workspace = true
github-actions-expressions.workspace = true
github-actions-models.workspace = true
http-cache-reqwest.workspace = true
@ -63,6 +64,9 @@ tree-sitter-bash.workspace = true
tree-sitter-powershell.workspace = true
yamlpath.workspace = true
[build-dependencies]
csv.workspace = true
fst.workspace = true
[dev-dependencies]
assert_cmd.workspace = true

34
crates/zizmor/build.rs Normal file
View file

@ -0,0 +1,34 @@
use std::fs::File;
use std::io;
use fst::MapBuilder;
fn main() {
println!("cargo::rerun-if-changed=../../support/context-capabilities.csv");
let out_dir = std::env::var("OUT_DIR").unwrap();
let out_path = std::path::Path::new(&out_dir).join("context-capabilities.fst");
let out = io::BufWriter::new(File::create(out_path).unwrap());
let mut build = MapBuilder::new(out).unwrap();
let mut rdr = csv::ReaderBuilder::new()
.has_headers(false)
.from_path("../../support/context-capabilities.csv")
.unwrap();
for record in rdr.records() {
let record = record.unwrap();
let context = record.get(0).unwrap();
let capability = match record.get(1).unwrap() {
"arbitrary" => 0,
"structured" => 1,
"fixed" => 2,
_ => panic!("Unknown capability"),
};
build.insert(context, capability).unwrap();
}
build.finish().unwrap();
}

View file

@ -12,7 +12,8 @@
use std::sync::LazyLock;
use github_actions_expressions::{BinOp, Expr, UnOp, context::ContextPattern};
use fst::Map;
use github_actions_expressions::Expr;
use github_actions_models::{
common::{Uses, expr::LoE},
workflow::job::Strategy,
@ -34,65 +35,29 @@ audit_meta!(
"code injection via template expansion"
);
/// Context patterns that are believed to be always safe.
static SAFE_CONTEXT_PATTERNS: LazyLock<Vec<ContextPattern>> = LazyLock::new(|| {
[
// The action path is always safe.
"github.action_path",
// The GitHub event name (i.e. trigger) is itself safe.
"github.event_name",
// Safe keys within the otherwise generally unsafe github.event context.
"github.event.after", // hexadecimal SHA ref
"github.event.before", // hexadecimal SHA ref
"github.event.issue.number", // the issue's own number
"github.event.merge_group.base_sha", // hexadecimal SHA ref
"github.event.number",
"github.event.pull_request.base.sha", // hexadecimal SHA ref
"github.event.pull_request.head.sha", // hexadecimal SHA ref
"github.event.pull_request.head.repo.fork", // boolean
"github.event.pull_request.commits", // number of commits in PR
"github.event.pull_request.number", // the PR's own number
"github.event.workflow_run.id",
// Corresponds to the job ID, which is workflow-controlled
// but can only be [A-Za-z0-9-_].
// See: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_id
"github.job",
// Information about the GitHub repository
"github.repository",
"github.repository_id",
"github.repositoryUrl",
// Information about the GitHub repository owner (account/org or ID)
"github.repository_owner",
"github.repository_owner_id",
// Unique numbers assigned by GitHub for workflow runs
"github.run_attempt",
"github.run_id",
"github.run_number",
// Typically something like `https://github.com`; you have bigger problems if
// this is attacker-controlled.
"github.server_url",
// Always a 40-char SHA-1 reference.
"github.sha",
// Like `secrets.*`: not safe to expose, but safe to interpolate.
"github.token",
// GitHub Actions-controlled local directory.
"github.workspace",
// GitHub Actions-controller runner architecture.
"runner.arch",
// Debug logging is (1) or is not (0) enabled on GitHub Actions runner.
"runner.debug",
// GitHub Actions runner operating system.
"runner.os",
// GitHub Actions temporary directory, value controlled by the runner itself.
"runner.temp",
// GitHub Actions cached tool directory, value controlled by the runner itself.
"runner.tool_cache",
]
.iter()
.map(|s| ContextPattern::new(s).unwrap())
.collect()
static CONTEXT_CAPABILITIES_FST: LazyLock<Map<&[u8]>> = LazyLock::new(|| {
fst::Map::new(include_bytes!(concat!(env!("OUT_DIR"), "/context-capabilities.fst")).as_slice())
.expect("couldn't initialize context capabilities FST")
});
enum Capability {
Arbitrary,
Structured,
Fixed,
}
impl Capability {
fn from_context(context: &str) -> Option<Self> {
match CONTEXT_CAPABILITIES_FST.get(context) {
Some(0) => Some(Capability::Arbitrary),
Some(1) => Some(Capability::Structured),
Some(2) => Some(Capability::Fixed),
Some(_) => unreachable!("unexpected context capability"),
_ => None,
}
}
}
impl TemplateInjection {
fn script_with_location<'s>(
step: &impl StepCommon<'s>,
@ -131,50 +96,6 @@ impl TemplateInjection {
}
}
/// Checks whether an expression is "safe" for the purposes of template
/// injection.
///
/// In the context of template injection, a "safe" expression is one that
/// can only ever return a literal node (i.e. bool, number, string, etc.).
/// All branches/flows of the expression must uphold that invariant;
/// no taint tracking is currently done.
fn expr_is_safe(expr: &Expr) -> bool {
match expr {
Expr::Number(_) => true,
Expr::String(_) => true,
Expr::Boolean(_) => true,
Expr::Null => true,
// NOTE: Currently unreachable, since these only occur
// within Expr::Context and we handle that at the top-level.
Expr::Star | Expr::Identifier(_) | Expr::Index(_) => unreachable!(),
// NOTE: Some function calls may be safe, but for now
// we consider them all unsafe.
Expr::Call { .. } => false,
// We consider all context accesses unsafe. This isn't true,
// but our audit filters the safe ones later on.
Expr::Context { .. } => false,
Expr::BinOp { lhs, op, rhs } => {
match op {
// `==` and `!=` are always safe, since they evaluate to
// boolean rather than to the truthy value.
BinOp::Eq | BinOp::Neq => true,
// `&&` is safe if its RHS is safe, since && cannot
// short-circuit.
BinOp::And => Self::expr_is_safe(rhs),
// We consider all other binops safe if both sides are safe,
// regardless of the actual operation type. This could be
// refined to check only one side with taint information.
// TODO: Relax this for >/>=/</<=?
_ => Self::expr_is_safe(lhs) && Self::expr_is_safe(rhs),
}
}
Expr::UnOp { op, .. } => match op {
// !expr always produces a boolean.
UnOp::Not => true,
},
}
}
fn injectable_template_expressions<'s>(
&self,
run: &str,
@ -187,88 +108,129 @@ impl TemplateInjection {
continue;
};
if Self::expr_is_safe(&parsed) {
// Emit a pedantic finding for all expressions, since
// all template injections are code smells, even if unexploitable.
bad_expressions.push((
expr.as_raw().into(),
Severity::Unknown,
Confidence::Unknown,
Persona::Pedantic,
));
continue;
}
// Emit a blanket pedantic finding for the extracted expression
// since any expression in a code context is a code smell,
// even if unexploitable.
bad_expressions.push((
expr.as_raw().into(),
Severity::Unknown,
Confidence::Unknown,
Persona::Pedantic,
));
for context in parsed.dataflow_contexts() {
if context.child_of("secrets") {
// While not ideal, secret expansion is typically not exploitable.
continue;
} else if SAFE_CONTEXT_PATTERNS.iter().any(|pat| pat.matches(context)) {
continue;
} else if context.child_of("inputs") {
// TODO: Currently low confidence because we don't check the
// input's type. In the future, we should index back into
// the workflow's triggers and exclude input expansions
// from innocuous types, e.g. booleans.
bad_expressions.push((
context.as_str().into(),
Severity::High,
Confidence::Low,
Persona::default(),
));
} else if let Some(env) = context.pop_if("env") {
let env_is_static = step.env_is_static(env);
// Try and turn our context into a pattern for
// matching against the FST.
match context.as_pattern().as_deref() {
Some(context_pattern) => {
// Try and get the pattern's capability from our FST.
match Capability::from_context(context_pattern) {
// Fixed means no meaningful injectable structure.
Some(Capability::Fixed) => continue,
// Structured means some attacker-controllable
// structure, but not fully arbitrary.
Some(Capability::Structured) => {
bad_expressions.push((
context.as_str().into(),
Severity::Medium,
Confidence::High,
Persona::default(),
));
}
// Arbitrary means the context's expansion is
// fully attacker-controllable.
Some(Capability::Arbitrary) => {
bad_expressions.push((
context.as_str().into(),
Severity::High,
Confidence::High,
Persona::default(),
));
}
None => {
// Without a FST match, we fall back on heuristics.
if context.child_of("secrets") {
// While not ideal, secret expansion is typically not exploitable.
continue;
} else if context.child_of("inputs") {
// TODO: Currently low confidence because we don't check the
// input's type. In the future, we should index back into
// the workflow's triggers and exclude input expansions
// from innocuous types, e.g. booleans.
bad_expressions.push((
context.as_str().into(),
Severity::High,
Confidence::Low,
Persona::default(),
));
} else if let Some(env) = context.pop_if("env") {
let env_is_static = step.env_is_static(env);
if !env_is_static {
if !env_is_static {
bad_expressions.push((
context.as_str().into(),
Severity::Low,
Confidence::High,
Persona::default(),
));
}
} else if context.child_of("github") {
// TODO: Filter these more finely; not everything in the event
// context is actually attacker-controllable.
bad_expressions.push((
context.as_str().into(),
Severity::High,
Confidence::High,
Persona::default(),
));
} else if context.child_of("matrix") || context == "matrix" {
if let Some(Strategy { matrix, .. }) = step.strategy() {
let matrix_is_static = match matrix {
// The matrix is generated by an expression, meaning
// that it's trivially not static.
Some(LoE::Expr(_)) => false,
// The matrix may expand to static values according to the context
Some(inner) => models::Matrix::new(inner)
.expands_to_static_values(context.as_str()),
// Context specifies a matrix, but there is no matrix defined.
// This is an invalid workflow so there's no point in flagging it.
None => continue,
};
if !matrix_is_static {
bad_expressions.push((
context.as_str().into(),
Severity::Medium,
Confidence::Medium,
Persona::default(),
));
}
}
continue;
} else {
// All other contexts are typically not attacker controllable,
// but may be in obscure cases.
bad_expressions.push((
context.as_str().into(),
Severity::Informational,
Confidence::Low,
Persona::default(),
));
}
}
}
}
None => {
// If we couldn't turn the context into a pattern,
// we almost certainly have something like
// `call(...).foo.bar`.
bad_expressions.push((
context.as_str().into(),
Severity::Low,
Confidence::High,
Severity::Informational,
Confidence::Low,
Persona::default(),
));
}
} else if context.child_of("github") {
// TODO: Filter these more finely; not everything in the event
// context is actually attacker-controllable.
bad_expressions.push((
context.as_str().into(),
Severity::High,
Confidence::High,
Persona::default(),
));
} else if context.child_of("matrix") || context == "matrix" {
if let Some(Strategy { matrix, .. }) = step.strategy() {
let matrix_is_static = match matrix {
// The matrix is generated by an expression, meaning
// that it's trivially not static.
Some(LoE::Expr(_)) => false,
// The matrix may expand to static values according to the context
Some(inner) => models::Matrix::new(inner)
.expands_to_static_values(context.as_str()),
// Context specifies a matrix, but there is no matrix defined.
// This is an invalid workflow so there's no point in flagging it.
None => continue,
};
if !matrix_is_static {
bad_expressions.push((
context.as_str().into(),
Severity::Medium,
Confidence::Medium,
Persona::default(),
));
}
}
continue;
} else {
// All other contexts are typically not attacker controllable,
// but may be in obscure cases.
bad_expressions.push((
context.as_str().into(),
Severity::Informational,
Confidence::Low,
Persona::default(),
));
}
}
}
@ -331,56 +293,27 @@ impl Audit for TemplateInjection {
#[cfg(test)]
mod tests {
use super::{Expr, TemplateInjection};
use crate::audit::template_injection::Capability;
#[test]
fn test_expr_is_safe() {
let cases = &[
// Literals are always safe.
("true", true),
("false", true),
("1.0", true),
("null", true),
("'some string'", true),
// negation is always safe.
("!true", true),
("!some.context", true),
// == / != are always safe, even if their hands are not.
("true == true", true),
("'true' == true", true),
("some.context == true", true),
("contains(some.context, 'foo') != true", true),
// || is safe if both hands are safe.
("true || true", true),
("some.context || true", false),
("true || some.context", false),
// && is true if the RHS is safe.
("true && true", true),
("some.context && true", true),
("true && other.context", false),
("some.context && other.context", false),
// Index ops and function calls are unsafe.
("some.context[0]", false),
("some.context[*]", false),
("someFunction()", false),
("fromJSON(some.context)", false),
("toJSON(fromJSON(some.context))", false),
// Context accesses are unsafe.
("some.context", false),
("some.context.*.something", false),
// More complicated cases:
("some.condition && '--some-arg' || ''", true),
("some.condition && some.context || ''", false),
("some.condition && '--some-arg' || some.context", false),
(
"(github.actor != 'github-actions[bot]' && github.actor) || 'BrewTestBot'",
false,
fn test_capability_from_context() {
assert!(matches!(
Capability::from_context("github.event.workflow_run.triggering_actor.login"),
Some(Capability::Arbitrary)
));
assert!(matches!(
Capability::from_context(
"github.event.workflow_run.triggering_actor.organizations_url"
),
];
for (case, safe) in cases {
let expr = Expr::parse(case).unwrap();
assert_eq!(TemplateInjection::expr_is_safe(&expr), *safe, "{expr:#?}");
}
Some(Capability::Structured)
));
assert!(matches!(
Capability::from_context("github.event.type.is_enabled"),
Some(Capability::Fixed)
));
assert!(matches!(
Capability::from_context("runner.arch"),
Some(Capability::Fixed)
));
}
}

View file

@ -1076,4 +1076,4 @@ error[dangerous-triggers]: use of fundamentally insecure workflow trigger
|
= note: audit confidence → Medium
110 findings (15 suppressed): 0 unknown, 6 informational, 0 low, 37 medium, 52 high
122 findings (27 suppressed): 0 unknown, 6 informational, 0 low, 37 medium, 52 high

View file

@ -1,7 +1,6 @@
---
source: tests/integration/e2e.rs
source: crates/zizmor/tests/integration/e2e.rs
expression: "zizmor().offline(false).output(OutputMode::Both).args([\"--no-online-audits\",\n\"--collect=workflows-only\"]).input(\"python/cpython@f963239ff1f986742d4c6bab2ab7b73f5a4047f6\").run()?"
snapshot_kind: text
---
INFO zizmor: skipping impostor-commit: offline audits only requested
INFO zizmor: skipping ref-confusion: offline audits only requested
@ -184,4 +183,4 @@ error[unpinned-uses]: unpinned action reference
|
= note: audit confidence → High
48 findings (29 suppressed): 0 unknown, 0 informational, 1 low, 0 medium, 18 high
72 findings (53 suppressed): 0 unknown, 0 informational, 1 low, 0 medium, 18 high

View file

@ -1,5 +1,5 @@
---
source: tests/integration/e2e.rs
source: crates/zizmor/tests/integration/e2e.rs
expression: "zizmor().input(input_under_test(\"issue-612-repro/action.yml\")).run()?"
---
No findings to report. Good job! (2 ignored)
No findings to report. Good job! (2 ignored, 2 suppressed)

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"cache-poisoning/caching-not-configurable.yml\")).run()?"
---
error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
@ -18,4 +18,4 @@ error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache pois
|
= note: audit confidence → Low
1 finding: 0 unknown, 0 informational, 0 low, 0 medium, 1 high
2 findings (1 suppressed): 0 unknown, 0 informational, 0 low, 0 medium, 1 high

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"cache-poisoning/workflow-release-branch-trigger.yml\")).run()?"
---
error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
@ -18,4 +18,4 @@ error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache pois
|
= note: audit confidence → Low
1 finding: 0 unknown, 0 informational, 0 low, 0 medium, 1 high
2 findings (1 suppressed): 0 unknown, 0 informational, 0 low, 0 medium, 1 high

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"cache-poisoning/caching-enabled-by-default.yml\")).run()?"
---
error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
@ -15,4 +15,4 @@ error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache pois
|
= note: audit confidence → Low
1 finding: 0 unknown, 0 informational, 0 low, 0 medium, 1 high
2 findings (1 suppressed): 0 unknown, 0 informational, 0 low, 0 medium, 1 high

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"cache-poisoning/caching-opt-in-boolean-toggle.yml\")).run()?"
---
error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
@ -17,4 +17,4 @@ error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache pois
|
= note: audit confidence → Low
1 finding: 0 unknown, 0 informational, 0 low, 0 medium, 1 high
2 findings (1 suppressed): 0 unknown, 0 informational, 0 low, 0 medium, 1 high

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"cache-poisoning/caching-opt-out.yml\")).run()?"
---
No findings to report. Good job!
No findings to report. Good job! (1 suppressed)

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"cache-poisoning/no-cache-aware-steps.yml\")).run()?"
---
No findings to report. Good job!
No findings to report. Good job! (1 suppressed)

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"cache-poisoning/workflow-tag-trigger.yml\")).run()?"
---
error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack
@ -18,4 +18,4 @@ error[cache-poisoning]: runtime artifacts potentially vulnerable to a cache pois
|
= note: audit confidence → Low
1 finding: 0 unknown, 0 informational, 0 low, 0 medium, 1 high
2 findings (1 suppressed): 0 unknown, 0 informational, 0 low, 0 medium, 1 high

View file

@ -1,8 +1,9 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().offline(true).input(input_under_test(\"several-vulnerabilities.yml\")).args([\"--persona=auditor\",\n\"--format=github\"]).run()?"
---
::error file=@@INPUT@@,line=5,title=excessive-permissions::several-vulnerabilities.yml:5: overly broad permissions: uses write-all permissions
::error file=@@INPUT@@,line=11,title=excessive-permissions::several-vulnerabilities.yml:11: overly broad permissions: uses write-all permissions
::error file=@@INPUT@@,line=2,title=dangerous-triggers::several-vulnerabilities.yml:2: use of fundamentally insecure workflow trigger: pull_request_target is almost always used insecurely
::notice file=@@INPUT@@,line=15,title=template-injection::several-vulnerabilities.yml:15: code injection via template expansion: ${{ github.event.pull_request.title }} may expand into attacker-controllable code
::error file=@@INPUT@@,line=15,title=template-injection::several-vulnerabilities.yml:15: code injection via template expansion: github.event.pull_request.title may expand into attacker-controllable code

View file

@ -1,7 +1,6 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"obfuscation.yml\")).run()?"
snapshot_kind: text
---
help[obfuscation]: obfuscated usage of GitHub Actions features
--> @@INPUT@@:12:9
@ -187,4 +186,4 @@ help[obfuscation]: obfuscated usage of GitHub Actions features
|
= note: audit confidence → High
31 findings (1 ignored, 7 suppressed): 0 unknown, 0 informational, 23 low, 0 medium, 0 high
39 findings (1 ignored, 15 suppressed): 0 unknown, 0 informational, 23 low, 0 medium, 0 high

View file

@ -2,4 +2,4 @@
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"template-injection/issue-749-repro.yml\")).run()?"
---
No findings to report. Good job!
No findings to report. Good job! (1 suppressed)

View file

@ -1,7 +1,18 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"template-injection/template-injection-dynamic-matrix.yml\")).args([\"--persona=auditor\"]).run()?"
---
note[template-injection]: code injection via template expansion
--> @@INPUT@@:19:9
|
19 | - name: Please dont
| ----------------- note: this step
20 | / run: |
21 | | echo "doing a thing: ${{ matrix.dynamic }}"
| |______________________________________________________- note: ${{ matrix.dynamic }} may expand into attacker-controllable code
|
= note: audit confidence → Unknown
warning[template-injection]: code injection via template expansion
--> @@INPUT@@:19:9
|
@ -13,4 +24,4 @@ warning[template-injection]: code injection via template expansion
|
= note: audit confidence → Medium
1 finding: 0 unknown, 0 informational, 0 low, 1 medium, 0 high
2 findings: 1 unknown, 0 informational, 0 low, 1 medium, 0 high

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"template-injection/issue-22-repro.yml\")).run()?"
---
No findings to report. Good job! (2 suppressed)
No findings to report. Good job! (5 suppressed)

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"template-injection/pr-317-repro.yml\")).run()?"
---
warning[template-injection]: code injection via template expansion
@ -15,4 +15,4 @@ warning[template-injection]: code injection via template expansion
|
= note: audit confidence → Medium
1 finding: 0 unknown, 0 informational, 0 low, 1 medium, 0 high
2 findings (1 suppressed): 0 unknown, 0 informational, 0 low, 1 medium, 0 high

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"template-injection/static-env.yml\")).run()?"
---
help[template-injection]: code injection via template expansion
@ -35,4 +35,4 @@ help[template-injection]: code injection via template expansion
|
= note: audit confidence → High
3 findings: 0 unknown, 0 informational, 3 low, 0 medium, 0 high
9 findings (6 suppressed): 0 unknown, 0 informational, 3 low, 0 medium, 0 high

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"template-injection/issue-339-repro.yml\")).run()?"
---
info[template-injection]: code injection via template expansion
@ -14,4 +14,4 @@ info[template-injection]: code injection via template expansion
|
= note: audit confidence → Low
1 finding: 0 unknown, 1 informational, 0 low, 0 medium, 0 high
2 findings (1 suppressed): 0 unknown, 1 informational, 0 low, 0 medium, 0 high

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"template-injection/issue-418-repro.yml\")).run()?"
---
No findings to report. Good job!
No findings to report. Good job! (1 suppressed)

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"template-injection/pr-425-backstop/action.yml\")).run()?"
---
error[template-injection]: code injection via template expansion
@ -58,4 +58,4 @@ error[unpinned-uses]: unpinned action reference
|
= note: audit confidence → High
5 findings: 0 unknown, 0 informational, 0 low, 0 medium, 5 high
9 findings (4 suppressed): 0 unknown, 0 informational, 0 low, 0 medium, 5 high

View file

@ -1,5 +1,5 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"template-injection/false-positive-menagerie.yml\")).run()?"
---
No findings to report. Good job!
No findings to report. Good job! (6 suppressed)

View file

@ -1,5 +1,16 @@
---
source: tests/integration/snapshot.rs
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"template-injection/template-injection-static-matrix.yml\")).args([\"--persona=auditor\"]).run()?"
---
No findings to report. Good job!
note[template-injection]: code injection via template expansion
--> @@INPUT@@:18:9
|
18 | - name: Nothing to fear
| --------------------- note: this step
19 | / run: |
20 | | echo "issue created: ${{ matrix.frob }}"
| |___________________________________________________- note: ${{ matrix.frob }} may expand into attacker-controllable code
|
= note: audit confidence → Unknown
1 finding: 1 unknown, 0 informational, 0 low, 0 medium, 0 high

View file

@ -0,0 +1,31 @@
# patterns.yml: tests that we normalize contexts correctly for the FST
# lookup inside template-injection
name: patterns
on: workflow_run # zizmor: ignore[dangerous-triggers]
permissions: {}
jobs:
inject-me:
runs-on: ubuntu-latest
steps:
# these are safe and are correctly ignored by default by zizmor,
# since they're all instantiations of the same fixed-cap pattern present
# in the FST.
- name: safe
run: |
echo "${{ github.event.workflow_run.pull_requests.*.base.repo.id }}"
echo "${{ github.event.workflow_run.pull_requests[0].base.repo.id }}"
echo "${{ github.event.workflow_run.pull_requests[1].base.repo.id }}"
# these are unsafe and should be flagged by default by zizmor,
# since they're all instantiations of the same arbitrary-cap pattern
# present in the FST.
- name: unsafe
run: |
echo "${{ github.event.changes.new_discussion.labels.*.name }}"
echo "${{ github.event.changes.new_discussion.labels[0].name }}"
echo "${{ github.event.changes.new_discussion.labels[1].name }}"

View file

@ -13,10 +13,19 @@ of `zizmor`.
* `zizmor` now supports generating completions for Nushell (#838)
### Enhancements 🌱
* The [template-injection] audit has been rewritten, and is now significantly
more precise and general over contexts supplied via GitHub's webhook
payloads (i.e. `github.event.*`) (#745)
### Bug Fixes 🐛
* The [insecure-commands] now correctly detects different truthy
values in `ACTIONS_ALLOW_UNSECURE_COMMANDS` (#840)
* The [template-injection] audit now correctly emits pedantic findings
in a blanket manner, rather than filtering them based on the presence
of other findings (#745)
## v1.8.0
@ -40,8 +49,11 @@ of `zizmor`.
### Bug Fixes 🐛
* `zizmor` now correctly handles index-style contexts in the
[template-injection] audit (#800, #806)
* The [template-injection] audit no longer produces false positive findings
on alternative representations of the same context pattern.
For example, `github.event.pull_request.head.sha` is considered safe
but `github['event']['pull_request']['head']['sha']` was not previously
detected as equivalent to it (#800, #806)
## v1.7.0

View file

@ -15,3 +15,4 @@ bindings = "bin"
[dependency-groups]
docs = ["mkdocs ~= 1.6", "mkdocs-material[imaging] ~= 9.5"]
codegen = ["prance[osv]", "requests"]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,59 @@
# known-safe-contexts.txt
# one per line, comment lines begin with #
# The action path is always safe.
github.action_path
# The GitHub event name (i.e. trigger) is itself safe.
github.event_name
# hexadecimal SHA refs
github.event.after
github.event.before
github.event.merge_group.base_sha
github.event.pull_request.base.sha
github.event.pull_request.head.sha
# Corresponds to the job ID which is workflow-controlled
# but can only be [A-Za-z0-9-_].
# See: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_id
github.job
# Information about the GitHub repository
github.repository
github.repository_id
github.repositoryurl
# Information about the GitHub repository owner (account/org or ID)
github.repository_owner
github.repository_owner_id
# Unique numbers assigned by GitHub for workflow runs
github.run_attempt
github.run_id
github.run_number
# Typically something like `https://github.com`; you have bigger problems if
# this is attacker-controlled.
github.server_url
# Always a 40-char SHA-1 reference.
github.sha
# Like `secrets.*`: not safe to expose but safe to interpolate.
github.token
# GitHub Actions-controlled local directory.
github.workspace
# runner contexts
# GitHub Actions-controller runner architecture.
runner.arch
# Debug logging is (1) or is not (0) enabled on GitHub Actions runner.
runner.debug
# GitHub Actions runner operating system.
runner.os
# GitHub Actions temporary directory value controlled by the runner itself.
runner.temp
# GitHub Actions cached tool directory value controlled by the runner itself.
runner.tool_cache

368
support/webhooks-to-contexts.py Executable file
View file

@ -0,0 +1,368 @@
#!/usr/bin/env -S uv run --script --only-group codegen
# Retrieves the latest OpenAPI spec for GitHub's webhooks from
# @octokit/openapi-webhooks and walks the schemas to produce a
# mapping of context patterns to their expected expansion capabilities.
#
# For example, `github.event.pull_request.title` would be `arbitrary`
# because it can contain arbitrary attacker-controlled content, while
# `github.event.pull_request.user.id` would be `fixed` because `id`
# is a fixed numeric value. These patterns can include wildcards
# for arrays, e.g. `github.event.pull_request.labels.*.name`
# matches any context that indexes through the `labels` array.
import csv
import os
import sys
from collections import defaultdict
from collections.abc import Iterator
from operator import itemgetter
from pathlib import Path
from typing import Literal
import requests
from prance import ResolvingParser
_REF = os.environ.get("WEBHOOKS_REF", "main")
if _REF.startswith("v"):
_REF = f"refs/tags/{_REF}"
else:
_REF = f"refs/heads/{_REF}"
_WEBHOOKS_JSON_URL = f"https://github.com/octokit/openapi-webhooks/raw/{_REF}/packages/openapi-webhooks/generated/api.github.com.json"
_HERE = Path(__file__).parent
_KNOWN_SAFE_CONTEXTS = _HERE / "known-safe-contexts.txt"
assert _KNOWN_SAFE_CONTEXTS.is_file(), f"Missing {_KNOWN_SAFE_CONTEXTS}"
_OUT = _HERE / "context-capabilities.csv"
# A mapping of workflow trigger event names to subevents.
# Keep in sync with: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows
_WORKFLOW_TRIGGERS_TO_EVENTS: dict[str, list[str]] = {
"branch_protection_rule": ["created", "edited", "deleted"],
"check_run": [
"created",
"rerequested",
"completed",
"requested_action",
],
"check_suite": [
"completed",
],
"create": [], # no subevents
"delete": [], # no subevents
# GitHub's doesn't specify the subevent for `deployment` or `deployment_status`,
# but the docs imply that the subevent is `created`.
"deployment": ["created"],
"deployment_status": ["created"],
"discussion": [
"created",
"edited",
"deleted",
"transferred",
"pinned",
"unpinned",
"labeled",
"unlabeled",
"locked",
"unlocked",
"category_changed",
"answered",
"unanswered",
],
"discussion_comment": [
"created",
"edited",
"deleted",
],
"fork": [], # no subevents
"gollum": [], # no subevents
"issue_comment": [
"created",
"edited",
"deleted",
],
"issues": [
"opened",
"edited",
"deleted",
"transferred",
"pinned",
"unpinned",
"closed",
"reopened",
"assigned",
"unassigned",
"labeled",
"unlabeled",
"locked",
"unlocked",
"milestoned",
"demilestoned",
"typed",
"untyped",
],
"label": [
"created",
"edited",
"deleted",
],
"merge_group": ["checks_requested"],
"milestone": [
"created",
"closed",
"opened",
"edited",
"deleted",
],
"page_build": [], # no subevents
"public": [], # no subevents
"pull_request": [
"assigned",
"unassigned",
"labeled",
"unlabeled",
"opened",
"edited",
"closed",
"reopened",
"synchronize",
"converted_to_draft",
"locked",
"unlocked",
"enqueued",
"dequeued",
"milestoned",
"demilestoned",
"ready_for_review",
"review_requested",
"review_request_removed",
"auto_merge_enabled",
"auto_merge_disabled",
],
# Unused.
# "pull_request_comment": []
"pull_request_review": [
"submitted",
"edited",
"dismissed",
],
"pull_request_review_comment": [
"created",
"edited",
"deleted",
],
# Not a real webhook; same contents as `pull_request`.
# "pull_request_target": [],
"push": [], # no subevents
"registry_package": [
"published",
"updated",
],
"release": [
"published",
"unpublished",
"created",
"edited",
"deleted",
"prereleased",
"released",
],
# NOTE: GitHub's OpenAPI spec uses `sample` to provide an example payload.
"repository_dispatch": ["sample"], # custom subevents
# Not a webhook.
# "schedule": [],
"status": [], # no subevents
"watch": ["started"],
# Not a webhook; inherits its payload from the calling workflow.
# "workflow_call": [],
"workflow_dispatch": [], # no subevents
"workflow_run": [
"completed",
"in_progress",
"requested",
],
}
def log(msg: str) -> None:
print(f"[+] {msg}", file=sys.stderr)
# Represents the capability of an expanded expression from a
# webhook's payload.
# For example, `github.pull_request.title` would be `arbitrary` because
# it can contain arbitrary attacker-controlled content, while
# `github.pull_request.base.sha` would be `fixed` because the attacker
# can't influence its value in a structured manner. `structured` is a middle
# ground where the attacker can influence the value, but only in a limited way.
Capability = Literal["arbitrary"] | Literal["structured"] | Literal["fixed"]
def walk_schema(
schema: dict,
top: str,
*,
typ: str | None = None,
) -> Iterator[tuple[str, Capability]]:
"""
Walks the schema and returns a list of tuples of the form
(path, capability).
"""
if typ is None:
typ = schema.get("type")
# We might have a schema with a type like `["string", "null"]`.
# When this happens, we walk the schema for each type,
# returning capabilities for all variants for subsequent unification.
if isinstance(typ, list):
for subtype in typ:
yield from walk_schema(schema, top, typ=subtype)
return
# Similarly for allOf/anyOf/oneOf: we try each listed subtype.
subschemas = ["allOf", "anyOf", "oneOf"]
for subschema in subschemas:
if subschema in schema:
for subtype in schema[subschema]:
yield from walk_schema(subtype, top)
return
match typ:
case "object":
properties = schema.get("properties", {})
if not properties:
yield top, "arbitrary"
else:
for prop, prop_schema in properties.items():
yield from walk_schema(
prop_schema,
f"{top}.{prop}",
)
additional_properties = schema.get("additionalProperties")
match additional_properties:
case True | {}:
yield f"{top}.*", "arbitrary"
case False | None:
pass
case _:
# TODO: In principle additionalProperties can be a schema,
# which we should handle. However GitHub's OpenAPI spec
# doesn't appear to do this at the moment, so we
# churlishly ignore it.
assert False, (
f"Unknown additionalProperties: {additional_properties}"
)
case "array":
items = schema.get("items", {})
if not items:
assert False, f"Empty array schema: {schema}"
else:
yield from walk_schema(
items,
f"{top}.*",
)
case "boolean" | "integer" | "number" | "null":
yield (top, "fixed")
case "string":
format = schema.get("format")
match format:
case "date-time":
yield (top, "fixed")
case "uri" | "uri-template" | "email":
yield (top, "structured")
case None:
if "enum" in schema:
yield (top, "fixed")
else:
# No format and no enum means we can't make any assumptions.
yield (top, "arbitrary")
case _:
assert False, f"Unknown string format: {format}"
case _:
assert False, f"Unknown schema type: {typ}"
def unify_capabilities(old: Capability, new: Capability) -> Capability:
"""
Unify two capabilities in favor of the more permissive one.
"""
caps = {old, new}
if "arbitrary" in caps:
return "arbitrary"
if "structured" in caps:
return "structured"
return old
def process_schemas(
event: str, schemas: list[dict], patterns_to_capabilities: dict[str, Capability]
) -> dict[str, Capability]:
top = "github.event"
for schema in schemas:
for pattern, cap in walk_schema(schema, top):
if old_cap := patterns_to_capabilities.get(pattern):
cap = unify_capabilities(old_cap, cap)
patterns_to_capabilities[pattern] = cap
return patterns_to_capabilities
if __name__ == "__main__":
log("loading known safe contexts...")
safe_contexts = []
for line in _KNOWN_SAFE_CONTEXTS.open().readlines():
line = line.strip()
if not line or line.startswith("#"):
continue
safe_contexts.append(line)
log(f" ...{len(safe_contexts)} known safe contexts")
log(f"downloading OpenAPI spec (ref={_REF})...")
webhooks_json = requests.get(_WEBHOOKS_JSON_URL).text
log("resolving refs in OpenAPI spec, this will take a moment...")
# TODO: Optimize; this is ridiculously slow.
parser = ResolvingParser(spec_string=webhooks_json)
spec = parser.specification
log(" ...done")
# We only care about webhook payload schemas.
schemas = {
name: schema
for (name, schema) in spec["components"]["schemas"].items()
if name.startswith("webhook-")
}
log(f"isolated {len(schemas)} webhook payload schemas")
schemas_for_event: dict[str, list[dict]] = defaultdict(list)
for event, subevents in _WORKFLOW_TRIGGERS_TO_EVENTS.items():
if not subevents:
webhook_key = f"webhook-{event.replace('_', '-')}"
schemas_for_event[event].append(schemas[webhook_key])
for subevent in subevents:
webhook_key = (
f"webhook-{event.replace('_', '-')}-{subevent.replace('_', '-')}"
)
schemas_for_event[event].append(schemas[webhook_key])
patterns_to_capabilities: dict[str, Capability] = {}
for event, schemas in schemas_for_event.items():
log(f" {event} -> {len(schemas)} schemas")
process_schemas(event, schemas, patterns_to_capabilities)
# Finally, fill in with some hardcoded pattern, capability pairs.
for context in safe_contexts:
patterns_to_capabilities[context] = "fixed"
with _OUT.open("w") as io:
writer = csv.writer(io)
for pattern, cap in sorted(patterns_to_capabilities.items(), key=itemgetter(0)):
writer.writerow([pattern, cap])

509
uv.lock generated
View file

@ -1,5 +1,18 @@
version = 1
requires-python = ">=3.9"
resolution-markers = [
"python_full_version >= '3.10'",
"python_full_version < '3.10'",
]
[[package]]
name = "attrs"
version = "25.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 },
]
[[package]]
name = "babel"
@ -116,6 +129,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8c/52/b08750ce0bce45c143e1b5d7357ee8c55341b52bdef4b0f081af1eb248c2/cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", size = 181290 },
]
[[package]]
name = "chardet"
version = "5.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 },
]
[[package]]
name = "charset-normalizer"
version = "3.4.1"
@ -259,7 +281,7 @@ name = "importlib-metadata"
version = "8.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "zipp" },
{ name = "zipp", marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304 }
wheels = [
@ -278,6 +300,103 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 },
]
[[package]]
name = "jsonschema"
version = "4.17.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
{ name = "attrs", marker = "python_full_version < '3.10'" },
{ name = "pyrsistent", marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/36/3d/ca032d5ac064dff543aa13c984737795ac81abc9fb130cd2fcff17cfabc7/jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d", size = 297785 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/97/c698bd9350f307daad79dd740806e1a59becd693bd11443a0f531e3229b3/jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6", size = 90379 },
]
[[package]]
name = "jsonschema"
version = "4.23.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
]
dependencies = [
{ name = "attrs", marker = "python_full_version >= '3.10'" },
{ name = "jsonschema-specifications", marker = "python_full_version >= '3.10'" },
{ name = "referencing", marker = "python_full_version >= '3.10'" },
{ name = "rpds-py", marker = "python_full_version >= '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 },
]
[[package]]
name = "jsonschema-path"
version = "0.3.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pathable", marker = "python_full_version >= '3.10'" },
{ name = "pyyaml", marker = "python_full_version >= '3.10'" },
{ name = "referencing", marker = "python_full_version >= '3.10'" },
{ name = "requests", marker = "python_full_version >= '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6e/45/41ebc679c2a4fced6a722f624c18d658dee42612b83ea24c1caf7c0eb3a8/jsonschema_path-0.3.4.tar.gz", hash = "sha256:8365356039f16cc65fddffafda5f58766e34bebab7d6d105616ab52bc4297001", size = 11159 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/58/3485da8cb93d2f393bce453adeef16896751f14ba3e2024bc21dc9597646/jsonschema_path-0.3.4-py3-none-any.whl", hash = "sha256:f502191fdc2b22050f9a81c9237be9d27145b9001c55842bece5e94e382e52f8", size = 14810 },
]
[[package]]
name = "jsonschema-spec"
version = "0.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jsonschema", version = "4.17.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "pathable", marker = "python_full_version < '3.10'" },
{ name = "pyyaml", marker = "python_full_version < '3.10'" },
{ name = "requests", marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a3/d5/b1c6171af26a3086189c45fcc7145cb40cdb7ab6779806e9e321501f5251/jsonschema_spec-0.1.6.tar.gz", hash = "sha256:90215863b56e212086641956b20127ccbf6d8a3a38343dad01d6a74d19482f76", size = 10323 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/52/18/0c62b011d64158c2420594002f58b3ad30a8e982bf8e6b4f078df9e95b9b/jsonschema_spec-0.1.6-py3-none-any.whl", hash = "sha256:f2206d18c89d1824c1f775ba14ed039743b41a9167bd2c5bdb774b66b3ca0bbf", size = 12749 },
]
[[package]]
name = "jsonschema-specifications"
version = "2025.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "referencing", marker = "python_full_version >= '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437 },
]
[[package]]
name = "lazy-object-proxy"
version = "1.11.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/57/f9/1f56571ed82fb324f293661690635cf42c41deb8a70a6c9e6edc3e9bb3c8/lazy_object_proxy-1.11.0.tar.gz", hash = "sha256:18874411864c9fbbbaa47f9fc1dd7aea754c86cfde21278ef427639d1dd78e9c", size = 44736 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/21/c8/457f1555f066f5bacc44337141294153dc993b5e9132272ab54a64ee98a2/lazy_object_proxy-1.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:132bc8a34f2f2d662a851acfd1b93df769992ed1b81e2b1fda7db3e73b0d5a18", size = 28045 },
{ url = "https://files.pythonhosted.org/packages/18/33/3260b4f8de6f0942008479fee6950b2b40af11fc37dba23aa3672b0ce8a6/lazy_object_proxy-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:01261a3afd8621a1accb5682df2593dc7ec7d21d38f411011a5712dcd418fbed", size = 28441 },
{ url = "https://files.pythonhosted.org/packages/51/f6/eb645ca1ff7408bb69e9b1fe692cce1d74394efdbb40d6207096c0cd8381/lazy_object_proxy-1.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:090935756cc041e191f22f4f9c7fd4fe9a454717067adf5b1bbd2ce3046b556e", size = 28047 },
{ url = "https://files.pythonhosted.org/packages/13/9c/aabbe1e8b99b8b0edb846b49a517edd636355ac97364419d9ba05b8fa19f/lazy_object_proxy-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:76ec715017f06410f57df442c1a8d66e6b5f7035077785b129817f5ae58810a4", size = 28440 },
{ url = "https://files.pythonhosted.org/packages/4d/24/dae4759469e9cd318fef145f7cfac7318261b47b23a4701aa477b0c3b42c/lazy_object_proxy-1.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a9f39098e93a63618a79eef2889ae3cf0605f676cd4797fdfd49fcd7ddc318b", size = 28142 },
{ url = "https://files.pythonhosted.org/packages/de/0c/645a881f5f27952a02f24584d96f9f326748be06ded2cee25f8f8d1cd196/lazy_object_proxy-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ee13f67f4fcd044ef27bfccb1c93d39c100046fec1fad6e9a1fcdfd17492aeb3", size = 28380 },
{ url = "https://files.pythonhosted.org/packages/a8/0f/6e004f928f7ff5abae2b8e1f68835a3870252f886e006267702e1efc5c7b/lazy_object_proxy-1.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4c84eafd8dd15ea16f7d580758bc5c2ce1f752faec877bb2b1f9f827c329cd", size = 28149 },
{ url = "https://files.pythonhosted.org/packages/63/cb/b8363110e32cc1fd82dc91296315f775d37a39df1c1cfa976ec1803dac89/lazy_object_proxy-1.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:d2503427bda552d3aefcac92f81d9e7ca631e680a2268cbe62cd6a58de6409b7", size = 28389 },
{ url = "https://files.pythonhosted.org/packages/7b/89/68c50fcfd81e11480cd8ee7f654c9bd790a9053b9a0efe9983d46106f6a9/lazy_object_proxy-1.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0613116156801ab3fccb9e2b05ed83b08ea08c2517fdc6c6bc0d4697a1a376e3", size = 28777 },
{ url = "https://files.pythonhosted.org/packages/39/d0/7e967689e24de8ea6368ec33295f9abc94b9f3f0cd4571bfe148dc432190/lazy_object_proxy-1.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bb03c507d96b65f617a6337dedd604399d35face2cdf01526b913fb50c4cb6e8", size = 29598 },
{ url = "https://files.pythonhosted.org/packages/04/61/24ad1506df7edc9f8fa396fd5bbe66bdfb41c1f3d131a04802fb1a48615b/lazy_object_proxy-1.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28c174db37946f94b97a97b579932ff88f07b8d73a46b6b93322b9ac06794a3b", size = 28041 },
{ url = "https://files.pythonhosted.org/packages/a3/be/5d7a93ad2892584c7aaad3db78fd5b4c14020d6f076b1f736825b7bbe2b3/lazy_object_proxy-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:d662f0669e27704495ff1f647070eb8816931231c44e583f4d0701b7adf6272f", size = 28307 },
{ url = "https://files.pythonhosted.org/packages/e7/1e/fb441c07b6662ec1fc92b249225ba6e6e5221b05623cb0131d082f782edc/lazy_object_proxy-1.11.0-py3-none-any.whl", hash = "sha256:a56a5093d433341ff7da0e89f9b486031ccd222ec8e52ec84d0ec1cdc819674b", size = 16635 },
]
[[package]]
name = "markdown"
version = "3.7"
@ -444,6 +563,75 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728 },
]
[[package]]
name = "openapi-schema-validator"
version = "0.4.4"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
{ name = "jsonschema", version = "4.17.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "rfc3339-validator", marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f6/bc/ea1c532bba227c61cf57f228790b3544e73fa1f82832bb59f3272672d239/openapi_schema_validator-0.4.4.tar.gz", hash = "sha256:c573e2be2c783abae56c5a1486ab716ca96e09d1c3eab56020d1dc680aa57bf8", size = 11626 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8d/94/9cb35371ac834d56ba2bd8870cdab1be25dc664f3a7387f8057ac091916b/openapi_schema_validator-0.4.4-py3-none-any.whl", hash = "sha256:79f37f38ef9fd5206b924ed7a6f382cea7b649b3b56383c47f1906082b7b9015", size = 9017 },
]
[[package]]
name = "openapi-schema-validator"
version = "0.6.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
]
dependencies = [
{ name = "jsonschema", version = "4.23.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "jsonschema-specifications", marker = "python_full_version >= '3.10'" },
{ name = "rfc3339-validator", marker = "python_full_version >= '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8b/f3/5507ad3325169347cd8ced61c232ff3df70e2b250c49f0fe140edb4973c6/openapi_schema_validator-0.6.3.tar.gz", hash = "sha256:f37bace4fc2a5d96692f4f8b31dc0f8d7400fd04f3a937798eaf880d425de6ee", size = 11550 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/21/c6/ad0fba32775ae749016829dace42ed80f4407b171da41313d1a3a5f102e4/openapi_schema_validator-0.6.3-py3-none-any.whl", hash = "sha256:f3b9870f4e556b5a62a1c39da72a6b4b16f3ad9c73dc80084b1b11e74ba148a3", size = 8755 },
]
[[package]]
name = "openapi-spec-validator"
version = "0.5.7"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
{ name = "jsonschema", version = "4.17.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "jsonschema-spec", marker = "python_full_version < '3.10'" },
{ name = "lazy-object-proxy", marker = "python_full_version < '3.10'" },
{ name = "openapi-schema-validator", version = "0.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/27/e5/9fe67dfd403777d500abb5cd650c16ef9d4918a6c12e87731c3a4feeb4ff/openapi_spec_validator-0.5.7.tar.gz", hash = "sha256:6c2d42180045a80fd6314de848b94310bdb0fa4949f4b099578b69f79d9fa5ac", size = 33722 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/68/05df6b11a082ee09b8b5319b3c8aeda58b8d3bccf89f201c45e8fe8a91d2/openapi_spec_validator-0.5.7-py3-none-any.whl", hash = "sha256:8712d2879db7692974ef89c47a3ebfc79436442921ec3a826ac0ce80cde8c549", size = 32429 },
]
[[package]]
name = "openapi-spec-validator"
version = "0.7.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
]
dependencies = [
{ name = "jsonschema", version = "4.23.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "jsonschema-path", marker = "python_full_version >= '3.10'" },
{ name = "lazy-object-proxy", marker = "python_full_version >= '3.10'" },
{ name = "openapi-schema-validator", version = "0.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/67/fe/21954ff978239dc29ebb313f5c87eeb4ec929b694b9667323086730998e2/openapi_spec_validator-0.7.1.tar.gz", hash = "sha256:8577b85a8268685da6f8aa30990b83b7960d4d1117e901d451b5d572605e5ec7", size = 37985 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2b/4d/e744fff95aaf3aeafc968d5ba7297c8cda0d1ecb8e3acd21b25adae4d835/openapi_spec_validator-0.7.1-py3-none-any.whl", hash = "sha256:3c81825043f24ccbcd2f4b149b11e8231abce5ba84f37065e14ec947d8f4e959", size = 38998 },
]
[[package]]
name = "packaging"
version = "24.2"
@ -462,6 +650,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 },
]
[[package]]
name = "pathable"
version = "0.4.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/67/93/8f2c2075b180c12c1e9f6a09d1a985bc2036906b13dff1d8917e395f2048/pathable-0.4.4.tar.gz", hash = "sha256:6905a3cd17804edfac7875b5f6c9142a218c7caef78693c2dbbbfbac186d88b2", size = 8124 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592 },
]
[[package]]
name = "pathspec"
version = "0.12.1"
@ -557,6 +754,53 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 },
]
[[package]]
name = "prance"
version = "23.6.21.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
{ name = "chardet", marker = "python_full_version < '3.10'" },
{ name = "packaging", marker = "python_full_version < '3.10'" },
{ name = "requests", marker = "python_full_version < '3.10'" },
{ name = "ruamel-yaml", marker = "python_full_version < '3.10'" },
{ name = "six", marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/73/f0/bcb5ffc8b7ab8e3d02dbef3bd945cf8fd6e12c146774f900659406b9fce1/prance-23.6.21.0.tar.gz", hash = "sha256:d8c15f8ac34019751cc4945f866d8d964d7888016d10de3592e339567177cabe", size = 2798776 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c9/db/4fb4901ee61274d0ab97746461fc5f2637e5d73aa73f34ee28e941a699a1/prance-23.6.21.0-py3-none-any.whl", hash = "sha256:6a4276fa07ed9f22feda4331097d7503c4adc3097e46ffae97425f2c1026bd9f", size = 36279 },
]
[package.optional-dependencies]
osv = [
{ name = "openapi-spec-validator", version = "0.5.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
]
[[package]]
name = "prance"
version = "25.4.8.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
]
dependencies = [
{ name = "chardet", marker = "python_full_version >= '3.10'" },
{ name = "packaging", marker = "python_full_version >= '3.10'" },
{ name = "requests", marker = "python_full_version >= '3.10'" },
{ name = "ruamel-yaml", marker = "python_full_version >= '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/5c/afa384b91354f0dbc194dfbea89bbd3e07dbe47d933a0a2c4fb989fc63af/prance-25.4.8.0.tar.gz", hash = "sha256:2f72d2983d0474b6f53fd604eb21690c1ebdb00d79a6331b7ec95fb4f25a1f65", size = 2808091 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/a8/fc509e514c708f43102542cdcbc2f42dc49f7a159f90f56d072371629731/prance-25.4.8.0-py3-none-any.whl", hash = "sha256:d3c362036d625b12aeee495621cb1555fd50b2af3632af3d825176bfb50e073b", size = 36386 },
]
[package.optional-dependencies]
osv = [
{ name = "openapi-spec-validator", version = "0.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
]
[[package]]
name = "pycparser"
version = "2.22"
@ -588,6 +832,39 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/00/ae/55d347eda5a4c57a2a042fe2e7616d14981115f566b9f8f69901aba3c0c6/pymdown_extensions-10.14-py3-none-any.whl", hash = "sha256:202481f716cc8250e4be8fce997781ebf7917701b59652458ee47f2401f818b5", size = 264264 },
]
[[package]]
name = "pyrsistent"
version = "0.20.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ce/3a/5031723c09068e9c8c2f0bc25c3a9245f2b1d1aea8396c787a408f2b95ca/pyrsistent-0.20.0.tar.gz", hash = "sha256:4c48f78f62ab596c679086084d0dd13254ae4f3d6c72a83ffdf5ebdef8f265a4", size = 103642 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/19/c343b14061907b629b765444b6436b160e2bd4184d17d4804bbe6381f6be/pyrsistent-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c3aba3e01235221e5b229a6c05f585f344734bd1ad42a8ac51493d74722bbce", size = 83416 },
{ url = "https://files.pythonhosted.org/packages/9f/4f/8342079ea331031ef9ed57edd312a9ad283bcc8adfaf268931ae356a09a6/pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1beb78af5423b879edaf23c5591ff292cf7c33979734c99aa66d5914ead880f", size = 118021 },
{ url = "https://files.pythonhosted.org/packages/d7/b7/64a125c488243965b7c5118352e47c6f89df95b4ac306d31cee409153d57/pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21cc459636983764e692b9eba7144cdd54fdec23ccdb1e8ba392a63666c60c34", size = 117747 },
{ url = "https://files.pythonhosted.org/packages/fe/a5/43c67bd5f80df9e7583042398d12113263ec57f27c0607abe9d78395d18f/pyrsistent-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5ac696f02b3fc01a710427585c855f65cd9c640e14f52abe52020722bb4906b", size = 114524 },
{ url = "https://files.pythonhosted.org/packages/8a/98/b382a87e89ca839106d874f7bf78d226b3eedb26735eb6f751f1a3375f21/pyrsistent-0.20.0-cp310-cp310-win32.whl", hash = "sha256:0724c506cd8b63c69c7f883cc233aac948c1ea946ea95996ad8b1380c25e1d3f", size = 60780 },
{ url = "https://files.pythonhosted.org/packages/37/8a/23e2193f7adea6901262e3cf39c7fe18ac0c446176c0ff0e19aeb2e9681e/pyrsistent-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:8441cf9616d642c475684d6cf2520dd24812e996ba9af15e606df5f6fd9d04a7", size = 63310 },
{ url = "https://files.pythonhosted.org/packages/df/63/7544dc7d0953294882a5c587fb1b10a26e0c23d9b92281a14c2514bac1f7/pyrsistent-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f3b1bcaa1f0629c978b355a7c37acd58907390149b7311b5db1b37648eb6958", size = 83481 },
{ url = "https://files.pythonhosted.org/packages/ae/a0/49249bc14d71b1bf2ffe89703acfa86f2017c25cfdabcaea532b8c8a5810/pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdd7ef1ea7a491ae70d826b6cc64868de09a1d5ff9ef8d574250d0940e275b8", size = 120222 },
{ url = "https://files.pythonhosted.org/packages/a1/94/9808e8c9271424120289b9028a657da336ad7e43da0647f62e4f6011d19b/pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cae40a9e3ce178415040a0383f00e8d68b569e97f31928a3a8ad37e3fde6df6a", size = 120002 },
{ url = "https://files.pythonhosted.org/packages/3f/f6/9ecfb78b2fc8e2540546db0fe19df1fae0f56664a5958c21ff8861b0f8da/pyrsistent-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6288b3fa6622ad8a91e6eb759cfc48ff3089e7c17fb1d4c59a919769314af224", size = 116850 },
{ url = "https://files.pythonhosted.org/packages/83/c8/e6d28bc27a0719f8eaae660357df9757d6e9ca9be2691595721de9e8adfc/pyrsistent-0.20.0-cp311-cp311-win32.whl", hash = "sha256:7d29c23bdf6e5438c755b941cef867ec2a4a172ceb9f50553b6ed70d50dfd656", size = 60775 },
{ url = "https://files.pythonhosted.org/packages/98/87/c6ef52ff30388f357922d08de012abdd3dc61e09311d88967bdae23ab657/pyrsistent-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:59a89bccd615551391f3237e00006a26bcf98a4d18623a19909a2c48b8e986ee", size = 63306 },
{ url = "https://files.pythonhosted.org/packages/15/ee/ff2ed52032ac1ce2e7ba19e79bd5b05d152ebfb77956cf08fcd6e8d760ea/pyrsistent-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:09848306523a3aba463c4b49493a760e7a6ca52e4826aa100ee99d8d39b7ad1e", size = 83537 },
{ url = "https://files.pythonhosted.org/packages/80/f1/338d0050b24c3132bcfc79b68c3a5f54bce3d213ecef74d37e988b971d8a/pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a14798c3005ec892bbada26485c2eea3b54109cb2533713e355c806891f63c5e", size = 122615 },
{ url = "https://files.pythonhosted.org/packages/07/3a/e56d6431b713518094fae6ff833a04a6f49ad0fbe25fb7c0dc7408e19d20/pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b14decb628fac50db5e02ee5a35a9c0772d20277824cfe845c8a8b717c15daa3", size = 122335 },
{ url = "https://files.pythonhosted.org/packages/4a/bb/5f40a4d5e985a43b43f607250e766cdec28904682c3505eb0bd343a4b7db/pyrsistent-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e2c116cc804d9b09ce9814d17df5edf1df0c624aba3b43bc1ad90411487036d", size = 118510 },
{ url = "https://files.pythonhosted.org/packages/1c/13/e6a22f40f5800af116c02c28e29f15c06aa41cb2036f6a64ab124647f28b/pyrsistent-0.20.0-cp312-cp312-win32.whl", hash = "sha256:e78d0c7c1e99a4a45c99143900ea0546025e41bb59ebc10182e947cf1ece9174", size = 60865 },
{ url = "https://files.pythonhosted.org/packages/75/ef/2fa3b55023ec07c22682c957808f9a41836da4cd006b5f55ec76bf0fbfa6/pyrsistent-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:4021a7f963d88ccd15b523787d18ed5e5269ce57aa4037146a2377ff607ae87d", size = 63239 },
{ url = "https://files.pythonhosted.org/packages/18/0c/289126299fcebf54fd01d385fb5176c328fef2c4233139c23dd48346e992/pyrsistent-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca52d1ceae015859d16aded12584c59eb3825f7b50c6cfd621d4231a6cc624ce", size = 83379 },
{ url = "https://files.pythonhosted.org/packages/4e/45/62639d53ac09eaafc00f2e5845565e70d3eddb2d296337a77637186ca03e/pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b318ca24db0f0518630e8b6f3831e9cba78f099ed5c1d65ffe3e023003043ba0", size = 117740 },
{ url = "https://files.pythonhosted.org/packages/ab/12/24b9a6ef7b991b6722756e0aa169a39463af2b8ed0fb526f0a00aae34ea4/pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed2c3216a605dc9a6ea50c7e84c82906e3684c4e80d2908208f662a6cbf9022", size = 117457 },
{ url = "https://files.pythonhosted.org/packages/19/3c/ab06510f86bc0934b77ade41948924ff1f33dcd3433f32feca2028218837/pyrsistent-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e14c95c16211d166f59c6611533d0dacce2e25de0f76e4c140fde250997b3ca", size = 114280 },
{ url = "https://files.pythonhosted.org/packages/ee/b1/1275bbfb929854d20e72aa2bbfb50ea3b1d7d41a95848b353691875e2817/pyrsistent-0.20.0-cp39-cp39-win32.whl", hash = "sha256:f058a615031eea4ef94ead6456f5ec2026c19fb5bd6bfe86e9665c4158cf802f", size = 60764 },
{ url = "https://files.pythonhosted.org/packages/28/77/0d7af973c0e3b1b83d8b45943601f77f85b943007e3a4d8744f7102c652b/pyrsistent-0.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:58b8f6366e152092194ae68fefe18b9f0b4f89227dfd86a07770c3d86097aebf", size = 63289 },
{ url = "https://files.pythonhosted.org/packages/23/88/0acd180010aaed4987c85700b7cc17f9505f3edb4e5873e4dc67f613e338/pyrsistent-0.20.0-py3-none-any.whl", hash = "sha256:c55acc4733aad6560a7f5f818466631f07efc001fd023f34a6c203f8b6df0f0b", size = 58106 },
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
@ -665,6 +942,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911 },
]
[[package]]
name = "referencing"
version = "0.36.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs", marker = "python_full_version >= '3.10'" },
{ name = "rpds-py", marker = "python_full_version >= '3.10'" },
{ name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 },
]
[[package]]
name = "regex"
version = "2024.11.6"
@ -765,6 +1056,204 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
]
[[package]]
name = "rfc3339-validator"
version = "0.1.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 },
]
[[package]]
name = "rpds-py"
version = "0.24.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0b/b3/52b213298a0ba7097c7ea96bee95e1947aa84cc816d48cebb539770cdf41/rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e", size = 26863 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/21/cbc43b220c9deb536b07fbd598c97d463bbb7afb788851891252fc920742/rpds_py-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:006f4342fe729a368c6df36578d7a348c7c716be1da0a1a0f86e3021f8e98724", size = 377531 },
{ url = "https://files.pythonhosted.org/packages/42/15/cc4b09ef160483e49c3aab3b56f3d375eadf19c87c48718fb0147e86a446/rpds_py-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d53747da70a4e4b17f559569d5f9506420966083a31c5fbd84e764461c4444b", size = 362273 },
{ url = "https://files.pythonhosted.org/packages/8c/a2/67718a188a88dbd5138d959bed6efe1cc7413a4caa8283bd46477ed0d1ad/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8acd55bd5b071156bae57b555f5d33697998752673b9de554dd82f5b5352727", size = 388111 },
{ url = "https://files.pythonhosted.org/packages/e5/e6/cbf1d3163405ad5f4a1a6d23f80245f2204d0c743b18525f34982dec7f4d/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7e80d375134ddb04231a53800503752093dbb65dad8dabacce2c84cccc78e964", size = 394447 },
{ url = "https://files.pythonhosted.org/packages/21/bb/4fe220ccc8a549b38b9e9cec66212dc3385a82a5ee9e37b54411cce4c898/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60748789e028d2a46fc1c70750454f83c6bdd0d05db50f5ae83e2db500b34da5", size = 448028 },
{ url = "https://files.pythonhosted.org/packages/a5/41/d2d6e0fd774818c4cadb94185d30cf3768de1c2a9e0143fc8bc6ce59389e/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e1daf5bf6c2be39654beae83ee6b9a12347cb5aced9a29eecf12a2d25fff664", size = 447410 },
{ url = "https://files.pythonhosted.org/packages/a7/a7/6d04d438f53d8bb2356bb000bea9cf5c96a9315e405b577117e344cc7404/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b221c2457d92a1fb3c97bee9095c874144d196f47c038462ae6e4a14436f7bc", size = 389531 },
{ url = "https://files.pythonhosted.org/packages/23/be/72e6df39bd7ca5a66799762bf54d8e702483fdad246585af96723109d486/rpds_py-0.24.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:66420986c9afff67ef0c5d1e4cdc2d0e5262f53ad11e4f90e5e22448df485bf0", size = 420099 },
{ url = "https://files.pythonhosted.org/packages/8c/c9/ca100cd4688ee0aa266197a5cb9f685231676dd7d573041ca53787b23f4e/rpds_py-0.24.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:43dba99f00f1d37b2a0265a259592d05fcc8e7c19d140fe51c6e6f16faabeb1f", size = 564950 },
{ url = "https://files.pythonhosted.org/packages/05/98/908cd95686d33b3ac8ac2e582d7ae38e2c3aa2c0377bf1f5663bafd1ffb2/rpds_py-0.24.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a88c0d17d039333a41d9bf4616bd062f0bd7aa0edeb6cafe00a2fc2a804e944f", size = 591778 },
{ url = "https://files.pythonhosted.org/packages/7b/ac/e143726f1dd3215efcb974b50b03bd08a8a1556b404a0a7872af6d197e57/rpds_py-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc31e13ce212e14a539d430428cd365e74f8b2d534f8bc22dd4c9c55b277b875", size = 560421 },
{ url = "https://files.pythonhosted.org/packages/60/28/add1c1d2fcd5aa354f7225d036d4492261759a22d449cff14841ef36a514/rpds_py-0.24.0-cp310-cp310-win32.whl", hash = "sha256:fc2c1e1b00f88317d9de6b2c2b39b012ebbfe35fe5e7bef980fd2a91f6100a07", size = 222089 },
{ url = "https://files.pythonhosted.org/packages/b0/ac/81f8066c6de44c507caca488ba336ae30d35d57f61fe10578824d1a70196/rpds_py-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0145295ca415668420ad142ee42189f78d27af806fcf1f32a18e51d47dd2052", size = 234622 },
{ url = "https://files.pythonhosted.org/packages/80/e6/c1458bbfb257448fdb2528071f1f4e19e26798ed5ef6d47d7aab0cb69661/rpds_py-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef", size = 377679 },
{ url = "https://files.pythonhosted.org/packages/dd/26/ea4181ef78f58b2c167548c6a833d7dc22408e5b3b181bda9dda440bb92d/rpds_py-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97", size = 362571 },
{ url = "https://files.pythonhosted.org/packages/56/fa/1ec54dd492c64c280a2249a047fc3369e2789dc474eac20445ebfc72934b/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e", size = 388012 },
{ url = "https://files.pythonhosted.org/packages/3a/be/bad8b0e0f7e58ef4973bb75e91c472a7d51da1977ed43b09989264bf065c/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d", size = 394730 },
{ url = "https://files.pythonhosted.org/packages/35/56/ab417fc90c21826df048fc16e55316ac40876e4b790104ececcbce813d8f/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586", size = 448264 },
{ url = "https://files.pythonhosted.org/packages/b6/75/4c63862d5c05408589196c8440a35a14ea4ae337fa70ded1f03638373f06/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4", size = 446813 },
{ url = "https://files.pythonhosted.org/packages/e7/0c/91cf17dffa9a38835869797a9f041056091ebba6a53963d3641207e3d467/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae", size = 389438 },
{ url = "https://files.pythonhosted.org/packages/1b/b0/60e6c72727c978276e02851819f3986bc40668f115be72c1bc4d922c950f/rpds_py-0.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc", size = 420416 },
{ url = "https://files.pythonhosted.org/packages/a1/d7/f46f85b9f863fb59fd3c534b5c874c48bee86b19e93423b9da8784605415/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c", size = 565236 },
{ url = "https://files.pythonhosted.org/packages/2a/d1/1467620ded6dd70afc45ec822cdf8dfe7139537780d1f3905de143deb6fd/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c", size = 592016 },
{ url = "https://files.pythonhosted.org/packages/5d/13/fb1ded2e6adfaa0c0833106c42feb290973f665300f4facd5bf5d7891d9c/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718", size = 560123 },
{ url = "https://files.pythonhosted.org/packages/1e/df/09fc1857ac7cc2eb16465a7199c314cbce7edde53c8ef21d615410d7335b/rpds_py-0.24.0-cp311-cp311-win32.whl", hash = "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a", size = 222256 },
{ url = "https://files.pythonhosted.org/packages/ff/25/939b40bc4d54bf910e5ee60fb5af99262c92458f4948239e8c06b0b750e7/rpds_py-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6", size = 234718 },
{ url = "https://files.pythonhosted.org/packages/1a/e0/1c55f4a3be5f1ca1a4fd1f3ff1504a1478c1ed48d84de24574c4fa87e921/rpds_py-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205", size = 366945 },
{ url = "https://files.pythonhosted.org/packages/39/1b/a3501574fbf29118164314dbc800d568b8c1c7b3258b505360e8abb3902c/rpds_py-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7", size = 351935 },
{ url = "https://files.pythonhosted.org/packages/dc/47/77d3d71c55f6a374edde29f1aca0b2e547325ed00a9da820cabbc9497d2b/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9", size = 390817 },
{ url = "https://files.pythonhosted.org/packages/4e/ec/1e336ee27484379e19c7f9cc170f4217c608aee406d3ae3a2e45336bff36/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e", size = 401983 },
{ url = "https://files.pythonhosted.org/packages/07/f8/39b65cbc272c635eaea6d393c2ad1ccc81c39eca2db6723a0ca4b2108fce/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda", size = 451719 },
{ url = "https://files.pythonhosted.org/packages/32/05/05c2b27dd9c30432f31738afed0300659cb9415db0ff7429b05dfb09bbde/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e", size = 442546 },
{ url = "https://files.pythonhosted.org/packages/7d/e0/19383c8b5d509bd741532a47821c3e96acf4543d0832beba41b4434bcc49/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029", size = 393695 },
{ url = "https://files.pythonhosted.org/packages/9d/15/39f14e96d94981d0275715ae8ea564772237f3fa89bc3c21e24de934f2c7/rpds_py-0.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9", size = 427218 },
{ url = "https://files.pythonhosted.org/packages/22/b9/12da7124905a680f690da7a9de6f11de770b5e359f5649972f7181c8bf51/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7", size = 568062 },
{ url = "https://files.pythonhosted.org/packages/88/17/75229017a2143d915f6f803721a6d721eca24f2659c5718a538afa276b4f/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91", size = 596262 },
{ url = "https://files.pythonhosted.org/packages/aa/64/8e8a1d8bd1b6b638d6acb6d41ab2cec7f2067a5b8b4c9175703875159a7c/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56", size = 564306 },
{ url = "https://files.pythonhosted.org/packages/68/1c/a7eac8d8ed8cb234a9b1064647824c387753343c3fab6ed7c83481ed0be7/rpds_py-0.24.0-cp312-cp312-win32.whl", hash = "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30", size = 224281 },
{ url = "https://files.pythonhosted.org/packages/bb/46/b8b5424d1d21f2f2f3f2d468660085318d4f74a8df8289e3dd6ad224d488/rpds_py-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034", size = 239719 },
{ url = "https://files.pythonhosted.org/packages/9d/c3/3607abc770395bc6d5a00cb66385a5479fb8cd7416ddef90393b17ef4340/rpds_py-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c", size = 367072 },
{ url = "https://files.pythonhosted.org/packages/d8/35/8c7ee0fe465793e3af3298dc5a9f3013bd63e7a69df04ccfded8293a4982/rpds_py-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c", size = 351919 },
{ url = "https://files.pythonhosted.org/packages/91/d3/7e1b972501eb5466b9aca46a9c31bcbbdc3ea5a076e9ab33f4438c1d069d/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240", size = 390360 },
{ url = "https://files.pythonhosted.org/packages/a2/a8/ccabb50d3c91c26ad01f9b09a6a3b03e4502ce51a33867c38446df9f896b/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8", size = 400704 },
{ url = "https://files.pythonhosted.org/packages/53/ae/5fa5bf0f3bc6ce21b5ea88fc0ecd3a439e7cb09dd5f9ffb3dbe1b6894fc5/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8", size = 450839 },
{ url = "https://files.pythonhosted.org/packages/e3/ac/c4e18b36d9938247e2b54f6a03746f3183ca20e1edd7d3654796867f5100/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b", size = 441494 },
{ url = "https://files.pythonhosted.org/packages/bf/08/b543969c12a8f44db6c0f08ced009abf8f519191ca6985509e7c44102e3c/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d", size = 393185 },
{ url = "https://files.pythonhosted.org/packages/da/7e/f6eb6a7042ce708f9dfc781832a86063cea8a125bbe451d663697b51944f/rpds_py-0.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7", size = 426168 },
{ url = "https://files.pythonhosted.org/packages/38/b0/6cd2bb0509ac0b51af4bb138e145b7c4c902bb4b724d6fd143689d6e0383/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad", size = 567622 },
{ url = "https://files.pythonhosted.org/packages/64/b0/c401f4f077547d98e8b4c2ec6526a80e7cb04f519d416430ec1421ee9e0b/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120", size = 595435 },
{ url = "https://files.pythonhosted.org/packages/9f/ec/7993b6e803294c87b61c85bd63e11142ccfb2373cf88a61ec602abcbf9d6/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9", size = 563762 },
{ url = "https://files.pythonhosted.org/packages/1f/29/4508003204cb2f461dc2b83dd85f8aa2b915bc98fe6046b9d50d4aa05401/rpds_py-0.24.0-cp313-cp313-win32.whl", hash = "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143", size = 223510 },
{ url = "https://files.pythonhosted.org/packages/f9/12/09e048d1814195e01f354155fb772fb0854bd3450b5f5a82224b3a319f0e/rpds_py-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a", size = 239075 },
{ url = "https://files.pythonhosted.org/packages/d2/03/5027cde39bb2408d61e4dd0cf81f815949bb629932a6c8df1701d0257fc4/rpds_py-0.24.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114", size = 362974 },
{ url = "https://files.pythonhosted.org/packages/bf/10/24d374a2131b1ffafb783e436e770e42dfdb74b69a2cd25eba8c8b29d861/rpds_py-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405", size = 348730 },
{ url = "https://files.pythonhosted.org/packages/7a/d1/1ef88d0516d46cd8df12e5916966dbf716d5ec79b265eda56ba1b173398c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47", size = 387627 },
{ url = "https://files.pythonhosted.org/packages/4e/35/07339051b8b901ecefd449ebf8e5522e92bcb95e1078818cbfd9db8e573c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272", size = 394094 },
{ url = "https://files.pythonhosted.org/packages/dc/62/ee89ece19e0ba322b08734e95441952062391065c157bbd4f8802316b4f1/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd", size = 449639 },
{ url = "https://files.pythonhosted.org/packages/15/24/b30e9f9e71baa0b9dada3a4ab43d567c6b04a36d1cb531045f7a8a0a7439/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a", size = 438584 },
{ url = "https://files.pythonhosted.org/packages/28/d9/49f7b8f3b4147db13961e19d5e30077cd0854ccc08487026d2cb2142aa4a/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d", size = 391047 },
{ url = "https://files.pythonhosted.org/packages/49/b0/e66918d0972c33a259ba3cd7b7ff10ed8bd91dbcfcbec6367b21f026db75/rpds_py-0.24.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7", size = 418085 },
{ url = "https://files.pythonhosted.org/packages/e1/6b/99ed7ea0a94c7ae5520a21be77a82306aac9e4e715d4435076ead07d05c6/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d", size = 564498 },
{ url = "https://files.pythonhosted.org/packages/28/26/1cacfee6b800e6fb5f91acecc2e52f17dbf8b0796a7c984b4568b6d70e38/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797", size = 590202 },
{ url = "https://files.pythonhosted.org/packages/a9/9e/57bd2f9fba04a37cef673f9a66b11ca8c43ccdd50d386c455cd4380fe461/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c", size = 561771 },
{ url = "https://files.pythonhosted.org/packages/9f/cf/b719120f375ab970d1c297dbf8de1e3c9edd26fe92c0ed7178dd94b45992/rpds_py-0.24.0-cp313-cp313t-win32.whl", hash = "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba", size = 221195 },
{ url = "https://files.pythonhosted.org/packages/2d/e5/22865285789f3412ad0c3d7ec4dc0a3e86483b794be8a5d9ed5a19390900/rpds_py-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350", size = 237354 },
{ url = "https://files.pythonhosted.org/packages/22/ef/a194eaef0d0f2cd3f4c893c5b809a7458aaa7c0a64e60a45a72a04835ed4/rpds_py-0.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a36b452abbf29f68527cf52e181fced56685731c86b52e852053e38d8b60bc8d", size = 378126 },
{ url = "https://files.pythonhosted.org/packages/c3/8d/9a07f69933204c098760c884f03835ab8fb66e28d2d5f3dd6741720cf29c/rpds_py-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b3b397eefecec8e8e39fa65c630ef70a24b09141a6f9fc17b3c3a50bed6b50e", size = 362887 },
{ url = "https://files.pythonhosted.org/packages/29/74/315f42060f2e3cedd77d382a98484a68ef727bd3b5fd7b91825b859a3e85/rpds_py-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdabcd3beb2a6dca7027007473d8ef1c3b053347c76f685f5f060a00327b8b65", size = 388661 },
{ url = "https://files.pythonhosted.org/packages/29/22/7ee7bb2b25ecdfcf1265d5a51472814fe60b580f9e1e2746eed9c476310a/rpds_py-0.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5db385bacd0c43f24be92b60c857cf760b7f10d8234f4bd4be67b5b20a7c0b6b", size = 394993 },
{ url = "https://files.pythonhosted.org/packages/46/7b/5f40e278d81cd23eea6b88bbac62bacc27ed19412051a1fc4229e8f9367a/rpds_py-0.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8097b3422d020ff1c44effc40ae58e67d93e60d540a65649d2cdaf9466030791", size = 448706 },
{ url = "https://files.pythonhosted.org/packages/5a/7a/06aada7ecdb0d02fbc041daee998ae841882fcc8ed3c0f84e72d6832fef1/rpds_py-0.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493fe54318bed7d124ce272fc36adbf59d46729659b2c792e87c3b95649cdee9", size = 447369 },
{ url = "https://files.pythonhosted.org/packages/c6/f3/428a9367077268f852db9b3b68b6eda6ee4594ab7dc2d603a2c370619cc0/rpds_py-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8aa362811ccdc1f8dadcc916c6d47e554169ab79559319ae9fae7d7752d0d60c", size = 390012 },
{ url = "https://files.pythonhosted.org/packages/55/66/24b61f14cd54e525583404afe6e3c221b309d1abd4b0b597a566dd8ee42d/rpds_py-0.24.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8f9a6e7fd5434817526815f09ea27f2746c4a51ee11bb3439065f5fc754db58", size = 421576 },
{ url = "https://files.pythonhosted.org/packages/22/56/18b81a4f0550e0d4be700cdcf1415ebf250fd21f9a5a775843dd3588dbf6/rpds_py-0.24.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8205ee14463248d3349131bb8099efe15cd3ce83b8ef3ace63c7e976998e7124", size = 565562 },
{ url = "https://files.pythonhosted.org/packages/42/80/82a935d78f74974f82d38e83fb02430f8e8cc09ad35e06d9a5d2e9b907a7/rpds_py-0.24.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:921ae54f9ecba3b6325df425cf72c074cd469dea843fb5743a26ca7fb2ccb149", size = 592924 },
{ url = "https://files.pythonhosted.org/packages/0d/49/b717e7b93c2ca881d2dac8b23b3a87a4c30f7c762bfd3df0b3953e655f13/rpds_py-0.24.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:32bab0a56eac685828e00cc2f5d1200c548f8bc11f2e44abf311d6b548ce2e45", size = 560847 },
{ url = "https://files.pythonhosted.org/packages/1e/26/ba630a291238e7f42d25bc5569d152623f18c21e9183e506585b23325c48/rpds_py-0.24.0-cp39-cp39-win32.whl", hash = "sha256:f5c0ed12926dec1dfe7d645333ea59cf93f4d07750986a586f511c0bc61fe103", size = 222570 },
{ url = "https://files.pythonhosted.org/packages/2d/84/01126e25e21f2ed6e63ec4030f78793dfee1a21aff1842136353c9caaed9/rpds_py-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:afc6e35f344490faa8276b5f2f7cbf71f88bc2cda4328e00553bd451728c571f", size = 234931 },
{ url = "https://files.pythonhosted.org/packages/99/48/11dae46d0c7f7e156ca0971a83f89c510af0316cd5d42c771b7cef945f0c/rpds_py-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:619ca56a5468f933d940e1bf431c6f4e13bef8e688698b067ae68eb4f9b30e3a", size = 378224 },
{ url = "https://files.pythonhosted.org/packages/33/18/e8398d255369e35d312942f3bb8ecaff013c44968904891be2ab63b3aa94/rpds_py-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:4b28e5122829181de1898c2c97f81c0b3246d49f585f22743a1246420bb8d399", size = 363252 },
{ url = "https://files.pythonhosted.org/packages/17/39/dd73ba691f4df3e6834bf982de214086ac3359ab3ac035adfb30041570e3/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e5ab32cf9eb3647450bc74eb201b27c185d3857276162c101c0f8c6374e098", size = 388871 },
{ url = "https://files.pythonhosted.org/packages/2f/2e/da0530b25cabd0feca2a759b899d2df325069a94281eeea8ac44c6cfeff7/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:208b3a70a98cf3710e97cabdc308a51cd4f28aa6e7bb11de3d56cd8b74bab98d", size = 394766 },
{ url = "https://files.pythonhosted.org/packages/4c/ee/dd1c5040a431beb40fad4a5d7868acf343444b0bc43e627c71df2506538b/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbc4362e06f950c62cad3d4abf1191021b2ffaf0b31ac230fbf0526453eee75e", size = 448712 },
{ url = "https://files.pythonhosted.org/packages/f5/ec/6b93ffbb686be948e4d91ec76f4e6757f8551034b2a8176dd848103a1e34/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebea2821cdb5f9fef44933617be76185b80150632736f3d76e54829ab4a3b4d1", size = 447150 },
{ url = "https://files.pythonhosted.org/packages/55/d5/a1c23760adad85b432df074ced6f910dd28f222b8c60aeace5aeb9a6654e/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4df06c35465ef4d81799999bba810c68d29972bf1c31db61bfdb81dd9d5bb", size = 390662 },
{ url = "https://files.pythonhosted.org/packages/a5/f3/419cb1f9bfbd3a48c256528c156e00f3349e3edce5ad50cbc141e71f66a5/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3aa13bdf38630da298f2e0d77aca967b200b8cc1473ea05248f6c5e9c9bdb44", size = 421351 },
{ url = "https://files.pythonhosted.org/packages/98/8e/62d1a55078e5ede0b3b09f35e751fa35924a34a0d44d7c760743383cd54a/rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:041f00419e1da7a03c46042453598479f45be3d787eb837af382bfc169c0db33", size = 566074 },
{ url = "https://files.pythonhosted.org/packages/fc/69/b7d1003166d78685da032b3c4ff1599fa536a3cfe6e5ce2da87c9c431906/rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8754d872a5dfc3c5bf9c0e059e8107451364a30d9fd50f1f1a85c4fb9481164", size = 592398 },
{ url = "https://files.pythonhosted.org/packages/ea/a8/1c98bc99338c37faadd28dd667d336df7409d77b4da999506a0b6b1c0aa2/rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:896c41007931217a343eff197c34513c154267636c8056fb409eafd494c3dcdc", size = 561114 },
{ url = "https://files.pythonhosted.org/packages/2b/41/65c91443685a4c7b5f1dd271beadc4a3e063d57c3269221548dd9416e15c/rpds_py-0.24.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:92558d37d872e808944c3c96d0423b8604879a3d1c86fdad508d7ed91ea547d5", size = 235548 },
{ url = "https://files.pythonhosted.org/packages/65/53/40bcc246a8354530d51a26d2b5b9afd1deacfb0d79e67295cc74df362f52/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d", size = 378386 },
{ url = "https://files.pythonhosted.org/packages/80/b0/5ea97dd2f53e3618560aa1f9674e896e63dff95a9b796879a201bc4c1f00/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a", size = 363440 },
{ url = "https://files.pythonhosted.org/packages/57/9d/259b6eada6f747cdd60c9a5eb3efab15f6704c182547149926c38e5bd0d5/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5", size = 388816 },
{ url = "https://files.pythonhosted.org/packages/94/c1/faafc7183712f89f4b7620c3c15979ada13df137d35ef3011ae83e93b005/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d", size = 395058 },
{ url = "https://files.pythonhosted.org/packages/6c/96/d7fa9d2a7b7604a61da201cc0306a355006254942093779d7121c64700ce/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793", size = 448692 },
{ url = "https://files.pythonhosted.org/packages/96/37/a3146c6eebc65d6d8c96cc5ffdcdb6af2987412c789004213227fbe52467/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba", size = 446462 },
{ url = "https://files.pythonhosted.org/packages/1f/13/6481dfd9ac7de43acdaaa416e3a7da40bc4bb8f5c6ca85e794100aa54596/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea", size = 390460 },
{ url = "https://files.pythonhosted.org/packages/61/e1/37e36bce65e109543cc4ff8d23206908649023549604fa2e7fbeba5342f7/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032", size = 421609 },
{ url = "https://files.pythonhosted.org/packages/20/dd/1f1a923d6cd798b8582176aca8a0784676f1a0449fb6f07fce6ac1cdbfb6/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d", size = 565818 },
{ url = "https://files.pythonhosted.org/packages/56/ec/d8da6df6a1eb3a418944a17b1cb38dd430b9e5a2e972eafd2b06f10c7c46/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25", size = 592627 },
{ url = "https://files.pythonhosted.org/packages/b3/14/c492b9c7d5dd133e13f211ddea6bb9870f99e4f73932f11aa00bc09a9be9/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba", size = 560885 },
{ url = "https://files.pythonhosted.org/packages/ef/e2/16cbbd7aaa4deaaeef5c90fee8b485c8b3312094cdad31e8006f5a3e5e08/rpds_py-0.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e0f3ef95795efcd3b2ec3fe0a5bcfb5dadf5e3996ea2117427e524d4fbf309c6", size = 378245 },
{ url = "https://files.pythonhosted.org/packages/d4/8c/5024dd105bf0a515576b7df8aeeba6556ffdbe2d636dee172c1a30497dd1/rpds_py-0.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:2c13777ecdbbba2077670285dd1fe50828c8742f6a4119dbef6f83ea13ad10fb", size = 363461 },
{ url = "https://files.pythonhosted.org/packages/a4/6f/3a4efcfa2f4391b69f5d0ed3e6be5d2c5468c24fd2d15b712d2dbefc1749/rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e8d804c2ccd618417e96720ad5cd076a86fa3f8cb310ea386a3e6229bae7d1", size = 388839 },
{ url = "https://files.pythonhosted.org/packages/6c/d2/b8e5f0a0e97d295a0ebceb5265ef2e44c3d55e0d0f938d64a5ecfffa715e/rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd822f019ccccd75c832deb7aa040bb02d70a92eb15a2f16c7987b7ad4ee8d83", size = 394860 },
{ url = "https://files.pythonhosted.org/packages/90/e9/9f1f297bdbc5b871826ad790b6641fc40532d97917916e6bd9f87fdd128d/rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0047638c3aa0dbcd0ab99ed1e549bbf0e142c9ecc173b6492868432d8989a046", size = 449314 },
{ url = "https://files.pythonhosted.org/packages/06/ad/62ddbbaead31a1a22f0332958d0ea7c7aeed1b2536c6a51dd66dfae321a2/rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5b66d1b201cc71bc3081bc2f1fc36b0c1f268b773e03bbc39066651b9e18391", size = 446376 },
{ url = "https://files.pythonhosted.org/packages/82/a7/05b660d2f3789506e98be69aaf2ccde94e0fc49cd26cd78d7069bc5ba1b8/rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbcbb6db5582ea33ce46a5d20a5793134b5365110d84df4e30b9d37c6fd40ad3", size = 390560 },
{ url = "https://files.pythonhosted.org/packages/66/1b/79fa0abffb802ff817821a148ce752eaaab87ba3a6a5e6b9f244c00c73d0/rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63981feca3f110ed132fd217bf7768ee8ed738a55549883628ee3da75bb9cb78", size = 421225 },
{ url = "https://files.pythonhosted.org/packages/6e/9b/368893ad2f7b2ece42cad87c7ec71309b5d93188db28b307eadb48cd28e5/rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3a55fc10fdcbf1a4bd3c018eea422c52cf08700cf99c28b5cb10fe97ab77a0d3", size = 566071 },
{ url = "https://files.pythonhosted.org/packages/41/75/1cd0a654d300449411e6fd0821f83c1cfc7223da2e8109f586b4d9b89054/rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:c30ff468163a48535ee7e9bf21bd14c7a81147c0e58a36c1078289a8ca7af0bd", size = 592334 },
{ url = "https://files.pythonhosted.org/packages/31/33/5905e2a2e7612218e25307a9255fc8671b977449d40d62fe317775fe4939/rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:369d9c6d4c714e36d4a03957b4783217a3ccd1e222cdd67d464a3a479fc17796", size = 561111 },
{ url = "https://files.pythonhosted.org/packages/64/bd/f4cc34ac2261a7cb8a48bc90ce1e36dc05f1ec5ac3b4537def20be5df555/rpds_py-0.24.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:24795c099453e3721fda5d8ddd45f5dfcc8e5a547ce7b8e9da06fecc3832e26f", size = 235168 },
]
[[package]]
name = "ruamel-yaml"
version = "0.18.10"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ruamel-yaml-clib", marker = "python_full_version < '3.13' and platform_python_implementation == 'CPython'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ea/46/f44d8be06b85bc7c4d8c95d658be2b68f27711f279bf9dd0612a5e4794f5/ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58", size = 143447 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/36/dfc1ebc0081e6d39924a2cc53654497f967a084a436bb64402dfce4254d9/ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1", size = 117729 },
]
[[package]]
name = "ruamel-yaml-clib"
version = "0.2.12"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/70/57/40a958e863e299f0c74ef32a3bde9f2d1ea8d69669368c0c502a0997f57f/ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5", size = 131301 },
{ url = "https://files.pythonhosted.org/packages/98/a8/29a3eb437b12b95f50a6bcc3d7d7214301c6c529d8fdc227247fa84162b5/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969", size = 633728 },
{ url = "https://files.pythonhosted.org/packages/35/6d/ae05a87a3ad540259c3ad88d71275cbd1c0f2d30ae04c65dcbfb6dcd4b9f/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df", size = 722230 },
{ url = "https://files.pythonhosted.org/packages/7f/b7/20c6f3c0b656fe609675d69bc135c03aac9e3865912444be6339207b6648/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76", size = 686712 },
{ url = "https://files.pythonhosted.org/packages/cd/11/d12dbf683471f888d354dac59593873c2b45feb193c5e3e0f2ebf85e68b9/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6", size = 663936 },
{ url = "https://files.pythonhosted.org/packages/72/14/4c268f5077db5c83f743ee1daeb236269fa8577133a5cfa49f8b382baf13/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd", size = 696580 },
{ url = "https://files.pythonhosted.org/packages/30/fc/8cd12f189c6405a4c1cf37bd633aa740a9538c8e40497c231072d0fef5cf/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a", size = 663393 },
{ url = "https://files.pythonhosted.org/packages/80/29/c0a017b704aaf3cbf704989785cd9c5d5b8ccec2dae6ac0c53833c84e677/ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da", size = 100326 },
{ url = "https://files.pythonhosted.org/packages/3a/65/fa39d74db4e2d0cd252355732d966a460a41cd01c6353b820a0952432839/ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28", size = 118079 },
{ url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224 },
{ url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480 },
{ url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068 },
{ url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012 },
{ url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352 },
{ url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344 },
{ url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498 },
{ url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205 },
{ url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185 },
{ url = "https://files.pythonhosted.org/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433 },
{ url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362 },
{ url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118 },
{ url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497 },
{ url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042 },
{ url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831 },
{ url = "https://files.pythonhosted.org/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692 },
{ url = "https://files.pythonhosted.org/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777 },
{ url = "https://files.pythonhosted.org/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523 },
{ url = "https://files.pythonhosted.org/packages/29/00/4864119668d71a5fa45678f380b5923ff410701565821925c69780356ffa/ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a", size = 132011 },
{ url = "https://files.pythonhosted.org/packages/7f/5e/212f473a93ae78c669ffa0cb051e3fee1139cb2d385d2ae1653d64281507/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", size = 642488 },
{ url = "https://files.pythonhosted.org/packages/1f/8f/ecfbe2123ade605c49ef769788f79c38ddb1c8fa81e01f4dbf5cf1a44b16/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", size = 745066 },
{ url = "https://files.pythonhosted.org/packages/e2/a9/28f60726d29dfc01b8decdb385de4ced2ced9faeb37a847bd5cf26836815/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", size = 701785 },
{ url = "https://files.pythonhosted.org/packages/84/7e/8e7ec45920daa7f76046578e4f677a3215fe8f18ee30a9cb7627a19d9b4c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", size = 693017 },
{ url = "https://files.pythonhosted.org/packages/c5/b3/d650eaade4ca225f02a648321e1ab835b9d361c60d51150bac49063b83fa/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", size = 741270 },
{ url = "https://files.pythonhosted.org/packages/87/b8/01c29b924dcbbed75cc45b30c30d565d763b9c4d540545a0eeecffb8f09c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01", size = 709059 },
{ url = "https://files.pythonhosted.org/packages/30/8c/ed73f047a73638257aa9377ad356bea4d96125b305c34a28766f4445cc0f/ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6", size = 98583 },
{ url = "https://files.pythonhosted.org/packages/b0/85/e8e751d8791564dd333d5d9a4eab0a7a115f7e349595417fd50ecae3395c/ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", size = 115190 },
{ url = "https://files.pythonhosted.org/packages/e5/46/ccdef7a84ad745c37cb3d9a81790f28fbc9adf9c237dba682017b123294e/ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987", size = 131834 },
{ url = "https://files.pythonhosted.org/packages/29/09/932360f30ad1b7b79f08757e0a6fb8c5392a52cdcc182779158fe66d25ac/ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45", size = 636120 },
{ url = "https://files.pythonhosted.org/packages/a2/2a/5b27602e7a4344c1334e26bf4739746206b7a60a8acdba33a61473468b73/ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a0e060aace4c24dcaf71023bbd7d42674e3b230f7e7b97317baf1e953e5b519", size = 724914 },
{ url = "https://files.pythonhosted.org/packages/da/1c/23497017c554fc06ff5701b29355522cff850f626337fff35d9ab352cb18/ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7", size = 689072 },
{ url = "https://files.pythonhosted.org/packages/68/e6/f3d4ff3223f9ea49c3b7169ec0268e42bd49f87c70c0e3e853895e4a7ae2/ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285", size = 667091 },
{ url = "https://files.pythonhosted.org/packages/84/62/ead07043527642491e5011b143f44b81ef80f1025a96069b7210e0f2f0f3/ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed", size = 699111 },
{ url = "https://files.pythonhosted.org/packages/52/b3/fe4d84446f7e4887e3bea7ceff0a7df23790b5ed625f830e79ace88ebefb/ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7", size = 666365 },
{ url = "https://files.pythonhosted.org/packages/6e/b3/7feb99a00bfaa5c6868617bb7651308afde85e5a0b23cd187fe5de65feeb/ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12", size = 100863 },
{ url = "https://files.pythonhosted.org/packages/93/07/de635108684b7a5bb06e432b0930c5a04b6c59efe73bd966d8db3cc208f2/ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b", size = 118653 },
]
[[package]]
name = "six"
version = "1.17.0"
@ -786,6 +1275,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 },
]
[[package]]
name = "typing-extensions"
version = "4.13.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
]
[[package]]
name = "urllib3"
version = "2.3.0"
@ -855,6 +1353,11 @@ name = "zizmor"
source = { editable = "." }
[package.dev-dependencies]
codegen = [
{ name = "prance", version = "23.6.21.0", source = { registry = "https://pypi.org/simple" }, extra = ["osv"], marker = "python_full_version < '3.10'" },
{ name = "prance", version = "25.4.8.0", source = { registry = "https://pypi.org/simple" }, extra = ["osv"], marker = "python_full_version >= '3.10'" },
{ name = "requests" },
]
docs = [
{ name = "mkdocs" },
{ name = "mkdocs-material", extra = ["imaging"] },
@ -863,6 +1366,10 @@ docs = [
[package.metadata]
[package.metadata.requires-dev]
codegen = [
{ name = "prance", extras = ["osv"] },
{ name = "requests" },
]
docs = [
{ name = "mkdocs", specifier = "~=1.6" },
{ name = "mkdocs-material", extras = ["imaging"], specifier = "~=9.5" },