mirror of
https://github.com/zizmorcore/zizmor.git
synced 2025-12-23 08:47:33 +00:00
Extract context patterns from webhook schemata (#745)
This commit is contained in:
parent
855aa0b183
commit
46f61576d3
39 changed files with 5496 additions and 386 deletions
83
.github/workflows/codegen.yml
vendored
Normal file
83
.github/workflows/codegen.yml
vendored
Normal 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 }}
|
||||
40
.github/workflows/refresh-schemas.yml
vendored
40
.github/workflows/refresh-schemas.yml
vendored
|
|
@ -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
29
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
4
Makefile
4
Makefile
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 &[
|
||||
|
|
|
|||
|
|
@ -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 = &[
|
||||
|
|
|
|||
|
|
@ -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
34
crates/zizmor/build.rs
Normal 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();
|
||||
}
|
||||
|
|
@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }}"
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -15,3 +15,4 @@ bindings = "bin"
|
|||
|
||||
[dependency-groups]
|
||||
docs = ["mkdocs ~= 1.6", "mkdocs-material[imaging] ~= 9.5"]
|
||||
codegen = ["prance[osv]", "requests"]
|
||||
|
|
|
|||
4031
support/context-capabilities.csv
Normal file
4031
support/context-capabilities.csv
Normal file
File diff suppressed because it is too large
Load diff
59
support/known-safe-contexts.txt
Normal file
59
support/known-safe-contexts.txt
Normal 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
368
support/webhooks-to-contexts.py
Executable 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
509
uv.lock
generated
|
|
@ -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" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue