pybun-cli 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pybun_cli-0.2.0/pybun_cli.egg-info → pybun_cli-0.2.1}/PKG-INFO +222 -2
- {pybun_cli-0.2.0 → pybun_cli-0.2.1}/README.md +221 -1
- {pybun_cli-0.2.0 → pybun_cli-0.2.1/pybun_cli.egg-info}/PKG-INFO +222 -2
- {pybun_cli-0.2.0 → pybun_cli-0.2.1}/pyproject.toml +1 -1
- {pybun_cli-0.2.0 → pybun_cli-0.2.1}/LICENSE +0 -0
- {pybun_cli-0.2.0 → pybun_cli-0.2.1}/MANIFEST.in +0 -0
- {pybun_cli-0.2.0 → pybun_cli-0.2.1}/pybun/__init__.py +0 -0
- {pybun_cli-0.2.0 → pybun_cli-0.2.1}/pybun/__main__.py +0 -0
- {pybun_cli-0.2.0 → pybun_cli-0.2.1}/pybun/bootstrap.py +0 -0
- {pybun_cli-0.2.0 → pybun_cli-0.2.1}/pybun/cli.py +0 -0
- {pybun_cli-0.2.0 → pybun_cli-0.2.1}/pybun_cli.egg-info/SOURCES.txt +0 -0
- {pybun_cli-0.2.0 → pybun_cli-0.2.1}/pybun_cli.egg-info/dependency_links.txt +0 -0
- {pybun_cli-0.2.0 → pybun_cli-0.2.1}/pybun_cli.egg-info/entry_points.txt +0 -0
- {pybun_cli-0.2.0 → pybun_cli-0.2.1}/pybun_cli.egg-info/top_level.txt +0 -0
- {pybun_cli-0.2.0 → pybun_cli-0.2.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pybun-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: PyBun shim bootstrapper for signed releases
|
|
5
5
|
Author: PyBun Authors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -271,6 +271,18 @@ pybun x cowsay==6.1
|
|
|
271
271
|
pybun x black -- --check .
|
|
272
272
|
```
|
|
273
273
|
|
|
274
|
+
### Affected Tests
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
pybun test --affected
|
|
278
|
+
pybun test --affected --since main --format=json
|
|
279
|
+
pybun test --affected --discover --format=json
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`--changed` is an alias for `--affected`. Selection is repository-wide and cannot be combined with explicit paths or `--member`. MCP `pybun_test` uses the same service with `{"changed": true, "since": "main"}`.
|
|
283
|
+
|
|
284
|
+
JSON includes `selection` with `direct`, `transitive`, or `conservative` reasons and import chains. The target Python's standard AST parser reads project source without executing it. Imports and ancestor `conftest.py` dependencies are modeled; uncertain parsing, known dynamic imports, or non-Python/configuration changes conservatively select all tests. Import-root ambiguity may select extra tests. Arbitrary runtime file dependencies are not inferred. Unavailable git/runtime evidence returns an error rather than a verified empty selection.
|
|
285
|
+
|
|
274
286
|
### Python Version Management
|
|
275
287
|
|
|
276
288
|
```bash
|
|
@@ -368,6 +380,8 @@ pybun audit --severity-threshold=medium
|
|
|
368
380
|
pybun audit --fail-on=high
|
|
369
381
|
```
|
|
370
382
|
|
|
383
|
+
The scanner follows OSV batch pagination and fetches advisory details before filtering or suggesting fixes. Missing severity is reported as `unknown`, remains visible at every threshold, and fails any `--fail-on` gate. Failed advisory lookups fail the scan. `PYBUN_OSV_URL` can point to a mirror's batch endpoint; its sibling `vulns/<id>` endpoint must serve full advisories.
|
|
384
|
+
|
|
371
385
|
### Profile Management
|
|
372
386
|
|
|
373
387
|
```bash
|
|
@@ -438,6 +452,8 @@ pybun self update --dry-run
|
|
|
438
452
|
pybun self update --channel nightly
|
|
439
453
|
```
|
|
440
454
|
|
|
455
|
+
MCP exposes the same doctor remediation plan: call `pybun_doctor` with `{"fix": true}` to preview, or `{"fix": true, "apply": true}` to apply low-risk, auto-applicable fixes. `applied_fixes` includes before/after cache counts; `skipped_fixes` explains candidates requiring manual action. The existing `pybun_fix` tool continues to fix lint violations with Ruff.
|
|
456
|
+
|
|
441
457
|
## Sandbox usage
|
|
442
458
|
|
|
443
459
|
Use the sandbox for untrusted scripts or PEP 723 snippets:
|
|
@@ -445,7 +461,34 @@ Use the sandbox for untrusted scripts or PEP 723 snippets:
|
|
|
445
461
|
pybun --format=json run --sandbox examples/hello.py
|
|
446
462
|
pybun --format=json run --sandbox --allow-network -c "print('net ok')"
|
|
447
463
|
```
|
|
448
|
-
|
|
464
|
+
Use repeatable `--allow-net=host[:port]` to permit selected network destinations
|
|
465
|
+
and `--allow-run=executable-name-or-path` to permit trusted executables. For example:
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
pybun --format=json run --sandbox --allow-net=pypi.org:443 script.py
|
|
469
|
+
pybun --format=json run --sandbox --allow-run=/usr/bin/git script.py
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
`--allow-network` retains unrestricted network access. Hostnames are resolved
|
|
473
|
+
once at startup; permission includes those IP addresses (and hosts sharing them).
|
|
474
|
+
Ports are exact when provided; omit the port to allow all ports for that host.
|
|
475
|
+
Use brackets for IPv6, for example `[::1]:8080`. TCP and UDP sends, direct
|
|
476
|
+
connects, and DNS lookups are checked. Denials identify the target with
|
|
477
|
+
`E_SANDBOX_NET_DENIED` or `E_SANDBOX_RUN_DENIED` and a permission hint.
|
|
478
|
+
MCP `sandbox_policy` supports matching `allow_net` and `allow_run` arrays.
|
|
479
|
+
|
|
480
|
+
Executable names are resolved from the initial PATH and pinned to their real
|
|
481
|
+
paths. Subprocess PATH/cwd overrides cannot authorize a different executable.
|
|
482
|
+
Use explicit paths for direct exec/posix_spawn calls; raw fork and shell-command
|
|
483
|
+
APIs remain denied. An allowed executable is trusted: it can launch children and
|
|
484
|
+
access the host independently of Python guards. When `allow_run` is nonempty,
|
|
485
|
+
the blanket zero-child OS process limit is not applied; `resource_limits.unsupported`
|
|
486
|
+
reports `process`. Existing parent OS limits still apply.
|
|
487
|
+
|
|
488
|
+
These are Python runtime guards plus supported resource limits, not a kernel
|
|
489
|
+
containment boundary. Native extensions and trusted external programs require
|
|
490
|
+
an OS/container sandbox when hostile code containment is needed. Combine with
|
|
491
|
+
`--profile=prod` for production-like runs.
|
|
449
492
|
|
|
450
493
|
## Profiles
|
|
451
494
|
|
|
@@ -513,6 +556,121 @@ Requires `pip install pybun-cli`.
|
|
|
513
556
|
```
|
|
514
557
|
*Note: If `pybun` is not in the PATH, provide the absolute path (e.g., `/Users/username/bin/pybun`).*
|
|
515
558
|
|
|
559
|
+
### Server policy
|
|
560
|
+
|
|
561
|
+
MCP execution requires an operator-owned security policy. Configure it before
|
|
562
|
+
starting the server; requests cannot reload or override it. The server freezes
|
|
563
|
+
one configuration at startup, with this precedence: `PYBUN_MCP_POLICY` file,
|
|
564
|
+
then `.pybun/config.toml`, then `pyproject.toml`. A selected file with a missing
|
|
565
|
+
or malformed policy fails closed; it does not fall back to another file.
|
|
566
|
+
`pyproject.toml` and explicit files use `[tool.pybun.mcp.policy]`;
|
|
567
|
+
`.pybun/config.toml` also accepts `[mcp.policy]`.
|
|
568
|
+
|
|
569
|
+
For a restricted native execution server:
|
|
570
|
+
|
|
571
|
+
```toml
|
|
572
|
+
[tool.pybun.mcp.policy]
|
|
573
|
+
default_sandbox = true
|
|
574
|
+
require_sandbox = true
|
|
575
|
+
allow_network = false
|
|
576
|
+
max_execution_time_s = 30
|
|
577
|
+
max_memory_mb = 0 # Linux supports a nonzero memory cap
|
|
578
|
+
max_cpu_time_s = 0
|
|
579
|
+
allowed_working_directories = ["."]
|
|
580
|
+
allowed_backends = ["native"]
|
|
581
|
+
denied_tools = ["pybun_gc"]
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
Start it with `PYBUN_BACKEND=native pybun mcp serve --stdio`. Relative boundaries
|
|
585
|
+
are resolved against the startup directory; start at the workspace root or
|
|
586
|
+
explicitly authorize its ancestor workspace. Keep policy files under operator
|
|
587
|
+
control, outside agent-writable paths for deployments. Audit logs must also be
|
|
588
|
+
outside the effective writable project roots.
|
|
589
|
+
|
|
590
|
+
Every tool and its resource aliases pass through authorization before audit
|
|
591
|
+
setup, filesystem snapshots or execution. Responses expose `policy_applied` and
|
|
592
|
+
stable `E_MCP_POLICY_*` denial codes. Requested limits may be stricter; zero
|
|
593
|
+
(unlimited) cannot relax an operator cap. Required sandboxing and active caps
|
|
594
|
+
prevent `unsafe_no_sandbox` from bypassing enforcement. Disallowed network
|
|
595
|
+
permission also removes granular network and executable grants. Sandbox reads
|
|
596
|
+
and writes default to the startup project boundary; explicit paths, symlinks,
|
|
597
|
+
backend-derived workspaces and environment/lock outputs are checked against the
|
|
598
|
+
operator's authorized directories. System-environment installation is denied.
|
|
599
|
+
Write snapshots skip symlinks and generated trees (`target`, `.git`, `.venv`,
|
|
600
|
+
`venv`, `__pycache__`, `node_modules`), and are bounded to 2,000 entries, depth
|
|
601
|
+
32, 4 MiB per file and 16 MiB read per snapshot. Audit records expose
|
|
602
|
+
`file_snapshot_complete=false` whenever evidence is skipped or bounded; their
|
|
603
|
+
`file_writes` then describes only observed changes, not a complete write trace.
|
|
604
|
+
Pure in-memory environment comparisons remain available without execution policy.
|
|
605
|
+
|
|
606
|
+
Enforcement support is explicit: native inline code and ordinary scripts support
|
|
607
|
+
these execution limits. Bounded uv preparation and native PEP 723 preparation
|
|
608
|
+
cannot currently propagate all server limits, so incompatible calls return
|
|
609
|
+
`E_MCP_POLICY_UNSUPPORTED` before preparation. Other tool handlers that cannot
|
|
610
|
+
enforce a requested sandbox/network/resource restriction also return that code.
|
|
611
|
+
Memory caps are unavailable on macOS/Windows; CPU caps require Unix. A dry run
|
|
612
|
+
can preview a bounded uv call without starting the backend.
|
|
613
|
+
|
|
614
|
+
Operators who authorize trusted uv project operations can set
|
|
615
|
+
`require_sandbox=false`, `allow_network=true`, `max_execution_time_s=0`,
|
|
616
|
+
`max_memory_mb=0`, and `max_cpu_time_s=0`; execution-time restrictions that a tool
|
|
617
|
+
cannot enforce must be disabled explicitly. Its existing backend deadlines still
|
|
618
|
+
apply. This does not grant requests permission to escape working-directory or
|
|
619
|
+
backend boundaries. The Python guard's documented containment limits still apply.
|
|
620
|
+
|
|
621
|
+
### Previewing execution
|
|
622
|
+
|
|
623
|
+
Call `pybun_run` with `dry_run: true` to return `would_execute`,
|
|
624
|
+
`estimated_risk` (`low`, `medium`, or `high`), `risk_reasons`, and
|
|
625
|
+
`sandbox_policy_that_would_apply` without executing code or preparing a backend.
|
|
626
|
+
The existing `risk_level` and `sandbox_policy` fields remain equivalent aliases.
|
|
627
|
+
The response also includes server policy and admission limits; previews consume
|
|
628
|
+
normal tool quota. Previewing an unsupported bounded backend reports
|
|
629
|
+
`policy_applied.execution_supported=false` and its denial code.
|
|
630
|
+
|
|
631
|
+
Risk is a substring heuristic over inline code or at most 2 MiB of regular UTF-8
|
|
632
|
+
script source. Missing script targets are rejected. Non-text, oversized or unreadable source reports
|
|
633
|
+
uncertainty as `medium`; filenames are not treated as source. If both are supplied,
|
|
634
|
+
inline code takes precedence, matching execution. `low` is not proof of safety:
|
|
635
|
+
dynamic imports, called code and later file changes can alter behavior. A preview
|
|
636
|
+
does not grant execution permission; the actual call is authorized independently.
|
|
637
|
+
|
|
638
|
+
### Tool rate limits
|
|
639
|
+
|
|
640
|
+
The same frozen configuration accepts `[tool.pybun.mcp.rate_limit]` (or
|
|
641
|
+
`[mcp.rate_limit]` in `.pybun/config.toml`):
|
|
642
|
+
|
|
643
|
+
```toml
|
|
644
|
+
[tool.pybun.mcp.rate_limit]
|
|
645
|
+
calls_per_minute = 60 # all calls in one stdio session
|
|
646
|
+
global_calls_per_minute = 600 # all sessions in this process/configuration
|
|
647
|
+
pybun_run_per_minute = 10
|
|
648
|
+
pybun_install_per_hour = 20
|
|
649
|
+
|
|
650
|
+
[tool.pybun.mcp.rate_limit.per_tool.pybun_lock]
|
|
651
|
+
calls = 5
|
|
652
|
+
window_secs = 60
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
These are token buckets: each starts with a full window's burst and refills
|
|
656
|
+
continuously using monotonic time. All applicable buckets are checked and debited
|
|
657
|
+
atomically after policy authorization, before filesystem snapshots, backend work
|
|
658
|
+
or subprocesses. Resource aliases share the corresponding tool's bucket. A denied
|
|
659
|
+
call debits no bucket; cancelling an admitted call retains its debit. Rejections
|
|
660
|
+
return `mcp.rate_limit_exceeded`, positive `retry_after_ms`, scope and applicable
|
|
661
|
+
limits in `rate_limit_applied`, and write a security audit event without invoking
|
|
662
|
+
the tool or producing its success receipt. Admitted responses expose their limits.
|
|
663
|
+
|
|
664
|
+
One stdio server owns one generated session UUID for its lifetime; client IDs and
|
|
665
|
+
arguments cannot change it. Global/per-tool quotas are shared by server instances
|
|
666
|
+
in the same process with the same canonical startup directory and frozen rate
|
|
667
|
+
configuration. Separate processes and restarts do not share quota state. State
|
|
668
|
+
is bounded to 64 configurations and 512 sessions per configuration; live entries
|
|
669
|
+
are never evicted to reset a quota. Idle sessions expire after two minutes and
|
|
670
|
+
configurations after twice their longest tool window (at least two hours).
|
|
671
|
+
Capacity exhaustion returns a retry hint. Configuration rejects unknown tool names
|
|
672
|
+
and fields, zero or more than 1,000,000 calls, and windows outside 1–86,400 seconds.
|
|
673
|
+
|
|
516
674
|
## JSON output examples
|
|
517
675
|
|
|
518
676
|
All commands support the `--format=json` option (schema v1). Examples:
|
|
@@ -686,3 +844,65 @@ Environment override: `PYBUN_TELEMETRY=0|1`
|
|
|
686
844
|
## License
|
|
687
845
|
|
|
688
846
|
MIT
|
|
847
|
+
|
|
848
|
+
### Local execution history
|
|
849
|
+
|
|
850
|
+
`pybun history --since 20m --format=json` queries CLI receipts for `run`, `test`,
|
|
851
|
+
`install`, `add`, and `remove`, including failed invocations. Receipts are appended
|
|
852
|
+
under `PYBUN_HOME/history.jsonl` (default `~/.cache/pybun/history.jsonl`), with a
|
|
853
|
+
private file mode on Unix and a file lock for concurrent writers. Existing files
|
|
854
|
+
with group/other permissions are rejected for appends; restrict them to mode 600. Set
|
|
855
|
+
`PYBUN_HISTORY=0` to disable recording. History queries never record themselves;
|
|
856
|
+
recording failures warn without changing the command's exit code.
|
|
857
|
+
|
|
858
|
+
Records contain timestamps, duration, exit code, lock digest when available,
|
|
859
|
+
backend identity, Python version/ABI, and known cache/policy/profile evidence.
|
|
860
|
+
Python observations identify whether they describe the execution interpreter or
|
|
861
|
+
the selected environment observed after the command; unavailable evidence is
|
|
862
|
+
`null`. Daemon use is false for the current direct execution paths. Inline code
|
|
863
|
+
and credential option values are removed, and the existing output redaction rules
|
|
864
|
+
apply before persistence and on reads. Program output and environment variables
|
|
865
|
+
are never stored. Arbitrary positional secrets cannot be inferred; disable
|
|
866
|
+
history when arguments contain such values. Queries accept positive `s`, `m`,
|
|
867
|
+
`h`, or `d` durations, report corrupt/partial lines without echoing them, and
|
|
868
|
+
reject files over 32 MiB; archive the local log when needed.
|
|
869
|
+
|
|
870
|
+
### Comparing captured environments
|
|
871
|
+
|
|
872
|
+
`pybun why <package> --format=json` explains every known declaration path to a
|
|
873
|
+
locked distribution. MCP `pybun_why` takes a `package` string and uses the same
|
|
874
|
+
service. The uv backend reads frozen, offline `uv workspace metadata`; it does
|
|
875
|
+
not resolve, install, or synchronize an environment. Paths retain markers and
|
|
876
|
+
classify direct, transitive, extra, dependency-group, and workspace provenance.
|
|
877
|
+
They describe lock alternatives rather than the currently active environment.
|
|
878
|
+
Results include backend/version, lock digest, `complete`, and diagnostics. An
|
|
879
|
+
unreachable or unavailable locked variant is explicitly `unknown`. Native
|
|
880
|
+
provenance uses current declarations with its lock and always reports partial
|
|
881
|
+
evidence. Traversal is bounded (10,000 nodes, 100,000 expansions, depth 256,
|
|
882
|
+
1,000 paths); reaching a bound also reports partial evidence.
|
|
883
|
+
|
|
884
|
+
`pybun environment diff before.json after.json --format=json` compares two JSON
|
|
885
|
+
captures, raw execution receipts, or JSON command envelopes. The MCP equivalent is
|
|
886
|
+
`pybun_environment_diff` with `before` and `after` objects; both use the same
|
|
887
|
+
read-only service. To compare CLI history entries, save the chosen `records[]`
|
|
888
|
+
objects from `pybun history --format=json` as the input files. Each input file is
|
|
889
|
+
limited to 4 MiB.
|
|
890
|
+
|
|
891
|
+
The response reports deterministic `changes` with reason codes for Python,
|
|
892
|
+
backend, lock, package, policy, profile and project evidence. `installed_packages`
|
|
893
|
+
describes interpreter-site inventory; `packages` separately describes a backend's
|
|
894
|
+
resolved lock inventory, which does not prove installation. History captures
|
|
895
|
+
standard isolated-venv site packages by reading distribution metadata without
|
|
896
|
+
importing project packages or running site hooks. System Python and venvs that
|
|
897
|
+
include system/user sites leave the installed inventory unavailable. Executable
|
|
898
|
+
digests are captured when readable; wheel artifact hashes remain unavailable
|
|
899
|
+
unless supplied by a capture.
|
|
900
|
+
|
|
901
|
+
Missing, masked and explicitly unknown values appear in `unavailable` and warning
|
|
902
|
+
diagnostics. They do not establish equality or a runtime state change. An identical
|
|
903
|
+
partial capture has an empty `changes` array while still reporting missing evidence.
|
|
904
|
+
Array ordering and normalized package spelling do not create differences. Only
|
|
905
|
+
typed, allowlisted metadata is compared; unrestricted environment variables and
|
|
906
|
+
unknown configuration keys are excluded, and existing redaction applies before
|
|
907
|
+
comparison. `causality: "not_inferred"` means the result describes recorded state
|
|
908
|
+
changes and never claims that they caused a program's output to change.
|
|
@@ -251,6 +251,18 @@ pybun x cowsay==6.1
|
|
|
251
251
|
pybun x black -- --check .
|
|
252
252
|
```
|
|
253
253
|
|
|
254
|
+
### Affected Tests
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
pybun test --affected
|
|
258
|
+
pybun test --affected --since main --format=json
|
|
259
|
+
pybun test --affected --discover --format=json
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`--changed` is an alias for `--affected`. Selection is repository-wide and cannot be combined with explicit paths or `--member`. MCP `pybun_test` uses the same service with `{"changed": true, "since": "main"}`.
|
|
263
|
+
|
|
264
|
+
JSON includes `selection` with `direct`, `transitive`, or `conservative` reasons and import chains. The target Python's standard AST parser reads project source without executing it. Imports and ancestor `conftest.py` dependencies are modeled; uncertain parsing, known dynamic imports, or non-Python/configuration changes conservatively select all tests. Import-root ambiguity may select extra tests. Arbitrary runtime file dependencies are not inferred. Unavailable git/runtime evidence returns an error rather than a verified empty selection.
|
|
265
|
+
|
|
254
266
|
### Python Version Management
|
|
255
267
|
|
|
256
268
|
```bash
|
|
@@ -348,6 +360,8 @@ pybun audit --severity-threshold=medium
|
|
|
348
360
|
pybun audit --fail-on=high
|
|
349
361
|
```
|
|
350
362
|
|
|
363
|
+
The scanner follows OSV batch pagination and fetches advisory details before filtering or suggesting fixes. Missing severity is reported as `unknown`, remains visible at every threshold, and fails any `--fail-on` gate. Failed advisory lookups fail the scan. `PYBUN_OSV_URL` can point to a mirror's batch endpoint; its sibling `vulns/<id>` endpoint must serve full advisories.
|
|
364
|
+
|
|
351
365
|
### Profile Management
|
|
352
366
|
|
|
353
367
|
```bash
|
|
@@ -418,6 +432,8 @@ pybun self update --dry-run
|
|
|
418
432
|
pybun self update --channel nightly
|
|
419
433
|
```
|
|
420
434
|
|
|
435
|
+
MCP exposes the same doctor remediation plan: call `pybun_doctor` with `{"fix": true}` to preview, or `{"fix": true, "apply": true}` to apply low-risk, auto-applicable fixes. `applied_fixes` includes before/after cache counts; `skipped_fixes` explains candidates requiring manual action. The existing `pybun_fix` tool continues to fix lint violations with Ruff.
|
|
436
|
+
|
|
421
437
|
## Sandbox usage
|
|
422
438
|
|
|
423
439
|
Use the sandbox for untrusted scripts or PEP 723 snippets:
|
|
@@ -425,7 +441,34 @@ Use the sandbox for untrusted scripts or PEP 723 snippets:
|
|
|
425
441
|
pybun --format=json run --sandbox examples/hello.py
|
|
426
442
|
pybun --format=json run --sandbox --allow-network -c "print('net ok')"
|
|
427
443
|
```
|
|
428
|
-
|
|
444
|
+
Use repeatable `--allow-net=host[:port]` to permit selected network destinations
|
|
445
|
+
and `--allow-run=executable-name-or-path` to permit trusted executables. For example:
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
pybun --format=json run --sandbox --allow-net=pypi.org:443 script.py
|
|
449
|
+
pybun --format=json run --sandbox --allow-run=/usr/bin/git script.py
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
`--allow-network` retains unrestricted network access. Hostnames are resolved
|
|
453
|
+
once at startup; permission includes those IP addresses (and hosts sharing them).
|
|
454
|
+
Ports are exact when provided; omit the port to allow all ports for that host.
|
|
455
|
+
Use brackets for IPv6, for example `[::1]:8080`. TCP and UDP sends, direct
|
|
456
|
+
connects, and DNS lookups are checked. Denials identify the target with
|
|
457
|
+
`E_SANDBOX_NET_DENIED` or `E_SANDBOX_RUN_DENIED` and a permission hint.
|
|
458
|
+
MCP `sandbox_policy` supports matching `allow_net` and `allow_run` arrays.
|
|
459
|
+
|
|
460
|
+
Executable names are resolved from the initial PATH and pinned to their real
|
|
461
|
+
paths. Subprocess PATH/cwd overrides cannot authorize a different executable.
|
|
462
|
+
Use explicit paths for direct exec/posix_spawn calls; raw fork and shell-command
|
|
463
|
+
APIs remain denied. An allowed executable is trusted: it can launch children and
|
|
464
|
+
access the host independently of Python guards. When `allow_run` is nonempty,
|
|
465
|
+
the blanket zero-child OS process limit is not applied; `resource_limits.unsupported`
|
|
466
|
+
reports `process`. Existing parent OS limits still apply.
|
|
467
|
+
|
|
468
|
+
These are Python runtime guards plus supported resource limits, not a kernel
|
|
469
|
+
containment boundary. Native extensions and trusted external programs require
|
|
470
|
+
an OS/container sandbox when hostile code containment is needed. Combine with
|
|
471
|
+
`--profile=prod` for production-like runs.
|
|
429
472
|
|
|
430
473
|
## Profiles
|
|
431
474
|
|
|
@@ -493,6 +536,121 @@ Requires `pip install pybun-cli`.
|
|
|
493
536
|
```
|
|
494
537
|
*Note: If `pybun` is not in the PATH, provide the absolute path (e.g., `/Users/username/bin/pybun`).*
|
|
495
538
|
|
|
539
|
+
### Server policy
|
|
540
|
+
|
|
541
|
+
MCP execution requires an operator-owned security policy. Configure it before
|
|
542
|
+
starting the server; requests cannot reload or override it. The server freezes
|
|
543
|
+
one configuration at startup, with this precedence: `PYBUN_MCP_POLICY` file,
|
|
544
|
+
then `.pybun/config.toml`, then `pyproject.toml`. A selected file with a missing
|
|
545
|
+
or malformed policy fails closed; it does not fall back to another file.
|
|
546
|
+
`pyproject.toml` and explicit files use `[tool.pybun.mcp.policy]`;
|
|
547
|
+
`.pybun/config.toml` also accepts `[mcp.policy]`.
|
|
548
|
+
|
|
549
|
+
For a restricted native execution server:
|
|
550
|
+
|
|
551
|
+
```toml
|
|
552
|
+
[tool.pybun.mcp.policy]
|
|
553
|
+
default_sandbox = true
|
|
554
|
+
require_sandbox = true
|
|
555
|
+
allow_network = false
|
|
556
|
+
max_execution_time_s = 30
|
|
557
|
+
max_memory_mb = 0 # Linux supports a nonzero memory cap
|
|
558
|
+
max_cpu_time_s = 0
|
|
559
|
+
allowed_working_directories = ["."]
|
|
560
|
+
allowed_backends = ["native"]
|
|
561
|
+
denied_tools = ["pybun_gc"]
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
Start it with `PYBUN_BACKEND=native pybun mcp serve --stdio`. Relative boundaries
|
|
565
|
+
are resolved against the startup directory; start at the workspace root or
|
|
566
|
+
explicitly authorize its ancestor workspace. Keep policy files under operator
|
|
567
|
+
control, outside agent-writable paths for deployments. Audit logs must also be
|
|
568
|
+
outside the effective writable project roots.
|
|
569
|
+
|
|
570
|
+
Every tool and its resource aliases pass through authorization before audit
|
|
571
|
+
setup, filesystem snapshots or execution. Responses expose `policy_applied` and
|
|
572
|
+
stable `E_MCP_POLICY_*` denial codes. Requested limits may be stricter; zero
|
|
573
|
+
(unlimited) cannot relax an operator cap. Required sandboxing and active caps
|
|
574
|
+
prevent `unsafe_no_sandbox` from bypassing enforcement. Disallowed network
|
|
575
|
+
permission also removes granular network and executable grants. Sandbox reads
|
|
576
|
+
and writes default to the startup project boundary; explicit paths, symlinks,
|
|
577
|
+
backend-derived workspaces and environment/lock outputs are checked against the
|
|
578
|
+
operator's authorized directories. System-environment installation is denied.
|
|
579
|
+
Write snapshots skip symlinks and generated trees (`target`, `.git`, `.venv`,
|
|
580
|
+
`venv`, `__pycache__`, `node_modules`), and are bounded to 2,000 entries, depth
|
|
581
|
+
32, 4 MiB per file and 16 MiB read per snapshot. Audit records expose
|
|
582
|
+
`file_snapshot_complete=false` whenever evidence is skipped or bounded; their
|
|
583
|
+
`file_writes` then describes only observed changes, not a complete write trace.
|
|
584
|
+
Pure in-memory environment comparisons remain available without execution policy.
|
|
585
|
+
|
|
586
|
+
Enforcement support is explicit: native inline code and ordinary scripts support
|
|
587
|
+
these execution limits. Bounded uv preparation and native PEP 723 preparation
|
|
588
|
+
cannot currently propagate all server limits, so incompatible calls return
|
|
589
|
+
`E_MCP_POLICY_UNSUPPORTED` before preparation. Other tool handlers that cannot
|
|
590
|
+
enforce a requested sandbox/network/resource restriction also return that code.
|
|
591
|
+
Memory caps are unavailable on macOS/Windows; CPU caps require Unix. A dry run
|
|
592
|
+
can preview a bounded uv call without starting the backend.
|
|
593
|
+
|
|
594
|
+
Operators who authorize trusted uv project operations can set
|
|
595
|
+
`require_sandbox=false`, `allow_network=true`, `max_execution_time_s=0`,
|
|
596
|
+
`max_memory_mb=0`, and `max_cpu_time_s=0`; execution-time restrictions that a tool
|
|
597
|
+
cannot enforce must be disabled explicitly. Its existing backend deadlines still
|
|
598
|
+
apply. This does not grant requests permission to escape working-directory or
|
|
599
|
+
backend boundaries. The Python guard's documented containment limits still apply.
|
|
600
|
+
|
|
601
|
+
### Previewing execution
|
|
602
|
+
|
|
603
|
+
Call `pybun_run` with `dry_run: true` to return `would_execute`,
|
|
604
|
+
`estimated_risk` (`low`, `medium`, or `high`), `risk_reasons`, and
|
|
605
|
+
`sandbox_policy_that_would_apply` without executing code or preparing a backend.
|
|
606
|
+
The existing `risk_level` and `sandbox_policy` fields remain equivalent aliases.
|
|
607
|
+
The response also includes server policy and admission limits; previews consume
|
|
608
|
+
normal tool quota. Previewing an unsupported bounded backend reports
|
|
609
|
+
`policy_applied.execution_supported=false` and its denial code.
|
|
610
|
+
|
|
611
|
+
Risk is a substring heuristic over inline code or at most 2 MiB of regular UTF-8
|
|
612
|
+
script source. Missing script targets are rejected. Non-text, oversized or unreadable source reports
|
|
613
|
+
uncertainty as `medium`; filenames are not treated as source. If both are supplied,
|
|
614
|
+
inline code takes precedence, matching execution. `low` is not proof of safety:
|
|
615
|
+
dynamic imports, called code and later file changes can alter behavior. A preview
|
|
616
|
+
does not grant execution permission; the actual call is authorized independently.
|
|
617
|
+
|
|
618
|
+
### Tool rate limits
|
|
619
|
+
|
|
620
|
+
The same frozen configuration accepts `[tool.pybun.mcp.rate_limit]` (or
|
|
621
|
+
`[mcp.rate_limit]` in `.pybun/config.toml`):
|
|
622
|
+
|
|
623
|
+
```toml
|
|
624
|
+
[tool.pybun.mcp.rate_limit]
|
|
625
|
+
calls_per_minute = 60 # all calls in one stdio session
|
|
626
|
+
global_calls_per_minute = 600 # all sessions in this process/configuration
|
|
627
|
+
pybun_run_per_minute = 10
|
|
628
|
+
pybun_install_per_hour = 20
|
|
629
|
+
|
|
630
|
+
[tool.pybun.mcp.rate_limit.per_tool.pybun_lock]
|
|
631
|
+
calls = 5
|
|
632
|
+
window_secs = 60
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
These are token buckets: each starts with a full window's burst and refills
|
|
636
|
+
continuously using monotonic time. All applicable buckets are checked and debited
|
|
637
|
+
atomically after policy authorization, before filesystem snapshots, backend work
|
|
638
|
+
or subprocesses. Resource aliases share the corresponding tool's bucket. A denied
|
|
639
|
+
call debits no bucket; cancelling an admitted call retains its debit. Rejections
|
|
640
|
+
return `mcp.rate_limit_exceeded`, positive `retry_after_ms`, scope and applicable
|
|
641
|
+
limits in `rate_limit_applied`, and write a security audit event without invoking
|
|
642
|
+
the tool or producing its success receipt. Admitted responses expose their limits.
|
|
643
|
+
|
|
644
|
+
One stdio server owns one generated session UUID for its lifetime; client IDs and
|
|
645
|
+
arguments cannot change it. Global/per-tool quotas are shared by server instances
|
|
646
|
+
in the same process with the same canonical startup directory and frozen rate
|
|
647
|
+
configuration. Separate processes and restarts do not share quota state. State
|
|
648
|
+
is bounded to 64 configurations and 512 sessions per configuration; live entries
|
|
649
|
+
are never evicted to reset a quota. Idle sessions expire after two minutes and
|
|
650
|
+
configurations after twice their longest tool window (at least two hours).
|
|
651
|
+
Capacity exhaustion returns a retry hint. Configuration rejects unknown tool names
|
|
652
|
+
and fields, zero or more than 1,000,000 calls, and windows outside 1–86,400 seconds.
|
|
653
|
+
|
|
496
654
|
## JSON output examples
|
|
497
655
|
|
|
498
656
|
All commands support the `--format=json` option (schema v1). Examples:
|
|
@@ -666,3 +824,65 @@ Environment override: `PYBUN_TELEMETRY=0|1`
|
|
|
666
824
|
## License
|
|
667
825
|
|
|
668
826
|
MIT
|
|
827
|
+
|
|
828
|
+
### Local execution history
|
|
829
|
+
|
|
830
|
+
`pybun history --since 20m --format=json` queries CLI receipts for `run`, `test`,
|
|
831
|
+
`install`, `add`, and `remove`, including failed invocations. Receipts are appended
|
|
832
|
+
under `PYBUN_HOME/history.jsonl` (default `~/.cache/pybun/history.jsonl`), with a
|
|
833
|
+
private file mode on Unix and a file lock for concurrent writers. Existing files
|
|
834
|
+
with group/other permissions are rejected for appends; restrict them to mode 600. Set
|
|
835
|
+
`PYBUN_HISTORY=0` to disable recording. History queries never record themselves;
|
|
836
|
+
recording failures warn without changing the command's exit code.
|
|
837
|
+
|
|
838
|
+
Records contain timestamps, duration, exit code, lock digest when available,
|
|
839
|
+
backend identity, Python version/ABI, and known cache/policy/profile evidence.
|
|
840
|
+
Python observations identify whether they describe the execution interpreter or
|
|
841
|
+
the selected environment observed after the command; unavailable evidence is
|
|
842
|
+
`null`. Daemon use is false for the current direct execution paths. Inline code
|
|
843
|
+
and credential option values are removed, and the existing output redaction rules
|
|
844
|
+
apply before persistence and on reads. Program output and environment variables
|
|
845
|
+
are never stored. Arbitrary positional secrets cannot be inferred; disable
|
|
846
|
+
history when arguments contain such values. Queries accept positive `s`, `m`,
|
|
847
|
+
`h`, or `d` durations, report corrupt/partial lines without echoing them, and
|
|
848
|
+
reject files over 32 MiB; archive the local log when needed.
|
|
849
|
+
|
|
850
|
+
### Comparing captured environments
|
|
851
|
+
|
|
852
|
+
`pybun why <package> --format=json` explains every known declaration path to a
|
|
853
|
+
locked distribution. MCP `pybun_why` takes a `package` string and uses the same
|
|
854
|
+
service. The uv backend reads frozen, offline `uv workspace metadata`; it does
|
|
855
|
+
not resolve, install, or synchronize an environment. Paths retain markers and
|
|
856
|
+
classify direct, transitive, extra, dependency-group, and workspace provenance.
|
|
857
|
+
They describe lock alternatives rather than the currently active environment.
|
|
858
|
+
Results include backend/version, lock digest, `complete`, and diagnostics. An
|
|
859
|
+
unreachable or unavailable locked variant is explicitly `unknown`. Native
|
|
860
|
+
provenance uses current declarations with its lock and always reports partial
|
|
861
|
+
evidence. Traversal is bounded (10,000 nodes, 100,000 expansions, depth 256,
|
|
862
|
+
1,000 paths); reaching a bound also reports partial evidence.
|
|
863
|
+
|
|
864
|
+
`pybun environment diff before.json after.json --format=json` compares two JSON
|
|
865
|
+
captures, raw execution receipts, or JSON command envelopes. The MCP equivalent is
|
|
866
|
+
`pybun_environment_diff` with `before` and `after` objects; both use the same
|
|
867
|
+
read-only service. To compare CLI history entries, save the chosen `records[]`
|
|
868
|
+
objects from `pybun history --format=json` as the input files. Each input file is
|
|
869
|
+
limited to 4 MiB.
|
|
870
|
+
|
|
871
|
+
The response reports deterministic `changes` with reason codes for Python,
|
|
872
|
+
backend, lock, package, policy, profile and project evidence. `installed_packages`
|
|
873
|
+
describes interpreter-site inventory; `packages` separately describes a backend's
|
|
874
|
+
resolved lock inventory, which does not prove installation. History captures
|
|
875
|
+
standard isolated-venv site packages by reading distribution metadata without
|
|
876
|
+
importing project packages or running site hooks. System Python and venvs that
|
|
877
|
+
include system/user sites leave the installed inventory unavailable. Executable
|
|
878
|
+
digests are captured when readable; wheel artifact hashes remain unavailable
|
|
879
|
+
unless supplied by a capture.
|
|
880
|
+
|
|
881
|
+
Missing, masked and explicitly unknown values appear in `unavailable` and warning
|
|
882
|
+
diagnostics. They do not establish equality or a runtime state change. An identical
|
|
883
|
+
partial capture has an empty `changes` array while still reporting missing evidence.
|
|
884
|
+
Array ordering and normalized package spelling do not create differences. Only
|
|
885
|
+
typed, allowlisted metadata is compared; unrestricted environment variables and
|
|
886
|
+
unknown configuration keys are excluded, and existing redaction applies before
|
|
887
|
+
comparison. `causality: "not_inferred"` means the result describes recorded state
|
|
888
|
+
changes and never claims that they caused a program's output to change.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pybun-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: PyBun shim bootstrapper for signed releases
|
|
5
5
|
Author: PyBun Authors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -271,6 +271,18 @@ pybun x cowsay==6.1
|
|
|
271
271
|
pybun x black -- --check .
|
|
272
272
|
```
|
|
273
273
|
|
|
274
|
+
### Affected Tests
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
pybun test --affected
|
|
278
|
+
pybun test --affected --since main --format=json
|
|
279
|
+
pybun test --affected --discover --format=json
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`--changed` is an alias for `--affected`. Selection is repository-wide and cannot be combined with explicit paths or `--member`. MCP `pybun_test` uses the same service with `{"changed": true, "since": "main"}`.
|
|
283
|
+
|
|
284
|
+
JSON includes `selection` with `direct`, `transitive`, or `conservative` reasons and import chains. The target Python's standard AST parser reads project source without executing it. Imports and ancestor `conftest.py` dependencies are modeled; uncertain parsing, known dynamic imports, or non-Python/configuration changes conservatively select all tests. Import-root ambiguity may select extra tests. Arbitrary runtime file dependencies are not inferred. Unavailable git/runtime evidence returns an error rather than a verified empty selection.
|
|
285
|
+
|
|
274
286
|
### Python Version Management
|
|
275
287
|
|
|
276
288
|
```bash
|
|
@@ -368,6 +380,8 @@ pybun audit --severity-threshold=medium
|
|
|
368
380
|
pybun audit --fail-on=high
|
|
369
381
|
```
|
|
370
382
|
|
|
383
|
+
The scanner follows OSV batch pagination and fetches advisory details before filtering or suggesting fixes. Missing severity is reported as `unknown`, remains visible at every threshold, and fails any `--fail-on` gate. Failed advisory lookups fail the scan. `PYBUN_OSV_URL` can point to a mirror's batch endpoint; its sibling `vulns/<id>` endpoint must serve full advisories.
|
|
384
|
+
|
|
371
385
|
### Profile Management
|
|
372
386
|
|
|
373
387
|
```bash
|
|
@@ -438,6 +452,8 @@ pybun self update --dry-run
|
|
|
438
452
|
pybun self update --channel nightly
|
|
439
453
|
```
|
|
440
454
|
|
|
455
|
+
MCP exposes the same doctor remediation plan: call `pybun_doctor` with `{"fix": true}` to preview, or `{"fix": true, "apply": true}` to apply low-risk, auto-applicable fixes. `applied_fixes` includes before/after cache counts; `skipped_fixes` explains candidates requiring manual action. The existing `pybun_fix` tool continues to fix lint violations with Ruff.
|
|
456
|
+
|
|
441
457
|
## Sandbox usage
|
|
442
458
|
|
|
443
459
|
Use the sandbox for untrusted scripts or PEP 723 snippets:
|
|
@@ -445,7 +461,34 @@ Use the sandbox for untrusted scripts or PEP 723 snippets:
|
|
|
445
461
|
pybun --format=json run --sandbox examples/hello.py
|
|
446
462
|
pybun --format=json run --sandbox --allow-network -c "print('net ok')"
|
|
447
463
|
```
|
|
448
|
-
|
|
464
|
+
Use repeatable `--allow-net=host[:port]` to permit selected network destinations
|
|
465
|
+
and `--allow-run=executable-name-or-path` to permit trusted executables. For example:
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
pybun --format=json run --sandbox --allow-net=pypi.org:443 script.py
|
|
469
|
+
pybun --format=json run --sandbox --allow-run=/usr/bin/git script.py
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
`--allow-network` retains unrestricted network access. Hostnames are resolved
|
|
473
|
+
once at startup; permission includes those IP addresses (and hosts sharing them).
|
|
474
|
+
Ports are exact when provided; omit the port to allow all ports for that host.
|
|
475
|
+
Use brackets for IPv6, for example `[::1]:8080`. TCP and UDP sends, direct
|
|
476
|
+
connects, and DNS lookups are checked. Denials identify the target with
|
|
477
|
+
`E_SANDBOX_NET_DENIED` or `E_SANDBOX_RUN_DENIED` and a permission hint.
|
|
478
|
+
MCP `sandbox_policy` supports matching `allow_net` and `allow_run` arrays.
|
|
479
|
+
|
|
480
|
+
Executable names are resolved from the initial PATH and pinned to their real
|
|
481
|
+
paths. Subprocess PATH/cwd overrides cannot authorize a different executable.
|
|
482
|
+
Use explicit paths for direct exec/posix_spawn calls; raw fork and shell-command
|
|
483
|
+
APIs remain denied. An allowed executable is trusted: it can launch children and
|
|
484
|
+
access the host independently of Python guards. When `allow_run` is nonempty,
|
|
485
|
+
the blanket zero-child OS process limit is not applied; `resource_limits.unsupported`
|
|
486
|
+
reports `process`. Existing parent OS limits still apply.
|
|
487
|
+
|
|
488
|
+
These are Python runtime guards plus supported resource limits, not a kernel
|
|
489
|
+
containment boundary. Native extensions and trusted external programs require
|
|
490
|
+
an OS/container sandbox when hostile code containment is needed. Combine with
|
|
491
|
+
`--profile=prod` for production-like runs.
|
|
449
492
|
|
|
450
493
|
## Profiles
|
|
451
494
|
|
|
@@ -513,6 +556,121 @@ Requires `pip install pybun-cli`.
|
|
|
513
556
|
```
|
|
514
557
|
*Note: If `pybun` is not in the PATH, provide the absolute path (e.g., `/Users/username/bin/pybun`).*
|
|
515
558
|
|
|
559
|
+
### Server policy
|
|
560
|
+
|
|
561
|
+
MCP execution requires an operator-owned security policy. Configure it before
|
|
562
|
+
starting the server; requests cannot reload or override it. The server freezes
|
|
563
|
+
one configuration at startup, with this precedence: `PYBUN_MCP_POLICY` file,
|
|
564
|
+
then `.pybun/config.toml`, then `pyproject.toml`. A selected file with a missing
|
|
565
|
+
or malformed policy fails closed; it does not fall back to another file.
|
|
566
|
+
`pyproject.toml` and explicit files use `[tool.pybun.mcp.policy]`;
|
|
567
|
+
`.pybun/config.toml` also accepts `[mcp.policy]`.
|
|
568
|
+
|
|
569
|
+
For a restricted native execution server:
|
|
570
|
+
|
|
571
|
+
```toml
|
|
572
|
+
[tool.pybun.mcp.policy]
|
|
573
|
+
default_sandbox = true
|
|
574
|
+
require_sandbox = true
|
|
575
|
+
allow_network = false
|
|
576
|
+
max_execution_time_s = 30
|
|
577
|
+
max_memory_mb = 0 # Linux supports a nonzero memory cap
|
|
578
|
+
max_cpu_time_s = 0
|
|
579
|
+
allowed_working_directories = ["."]
|
|
580
|
+
allowed_backends = ["native"]
|
|
581
|
+
denied_tools = ["pybun_gc"]
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
Start it with `PYBUN_BACKEND=native pybun mcp serve --stdio`. Relative boundaries
|
|
585
|
+
are resolved against the startup directory; start at the workspace root or
|
|
586
|
+
explicitly authorize its ancestor workspace. Keep policy files under operator
|
|
587
|
+
control, outside agent-writable paths for deployments. Audit logs must also be
|
|
588
|
+
outside the effective writable project roots.
|
|
589
|
+
|
|
590
|
+
Every tool and its resource aliases pass through authorization before audit
|
|
591
|
+
setup, filesystem snapshots or execution. Responses expose `policy_applied` and
|
|
592
|
+
stable `E_MCP_POLICY_*` denial codes. Requested limits may be stricter; zero
|
|
593
|
+
(unlimited) cannot relax an operator cap. Required sandboxing and active caps
|
|
594
|
+
prevent `unsafe_no_sandbox` from bypassing enforcement. Disallowed network
|
|
595
|
+
permission also removes granular network and executable grants. Sandbox reads
|
|
596
|
+
and writes default to the startup project boundary; explicit paths, symlinks,
|
|
597
|
+
backend-derived workspaces and environment/lock outputs are checked against the
|
|
598
|
+
operator's authorized directories. System-environment installation is denied.
|
|
599
|
+
Write snapshots skip symlinks and generated trees (`target`, `.git`, `.venv`,
|
|
600
|
+
`venv`, `__pycache__`, `node_modules`), and are bounded to 2,000 entries, depth
|
|
601
|
+
32, 4 MiB per file and 16 MiB read per snapshot. Audit records expose
|
|
602
|
+
`file_snapshot_complete=false` whenever evidence is skipped or bounded; their
|
|
603
|
+
`file_writes` then describes only observed changes, not a complete write trace.
|
|
604
|
+
Pure in-memory environment comparisons remain available without execution policy.
|
|
605
|
+
|
|
606
|
+
Enforcement support is explicit: native inline code and ordinary scripts support
|
|
607
|
+
these execution limits. Bounded uv preparation and native PEP 723 preparation
|
|
608
|
+
cannot currently propagate all server limits, so incompatible calls return
|
|
609
|
+
`E_MCP_POLICY_UNSUPPORTED` before preparation. Other tool handlers that cannot
|
|
610
|
+
enforce a requested sandbox/network/resource restriction also return that code.
|
|
611
|
+
Memory caps are unavailable on macOS/Windows; CPU caps require Unix. A dry run
|
|
612
|
+
can preview a bounded uv call without starting the backend.
|
|
613
|
+
|
|
614
|
+
Operators who authorize trusted uv project operations can set
|
|
615
|
+
`require_sandbox=false`, `allow_network=true`, `max_execution_time_s=0`,
|
|
616
|
+
`max_memory_mb=0`, and `max_cpu_time_s=0`; execution-time restrictions that a tool
|
|
617
|
+
cannot enforce must be disabled explicitly. Its existing backend deadlines still
|
|
618
|
+
apply. This does not grant requests permission to escape working-directory or
|
|
619
|
+
backend boundaries. The Python guard's documented containment limits still apply.
|
|
620
|
+
|
|
621
|
+
### Previewing execution
|
|
622
|
+
|
|
623
|
+
Call `pybun_run` with `dry_run: true` to return `would_execute`,
|
|
624
|
+
`estimated_risk` (`low`, `medium`, or `high`), `risk_reasons`, and
|
|
625
|
+
`sandbox_policy_that_would_apply` without executing code or preparing a backend.
|
|
626
|
+
The existing `risk_level` and `sandbox_policy` fields remain equivalent aliases.
|
|
627
|
+
The response also includes server policy and admission limits; previews consume
|
|
628
|
+
normal tool quota. Previewing an unsupported bounded backend reports
|
|
629
|
+
`policy_applied.execution_supported=false` and its denial code.
|
|
630
|
+
|
|
631
|
+
Risk is a substring heuristic over inline code or at most 2 MiB of regular UTF-8
|
|
632
|
+
script source. Missing script targets are rejected. Non-text, oversized or unreadable source reports
|
|
633
|
+
uncertainty as `medium`; filenames are not treated as source. If both are supplied,
|
|
634
|
+
inline code takes precedence, matching execution. `low` is not proof of safety:
|
|
635
|
+
dynamic imports, called code and later file changes can alter behavior. A preview
|
|
636
|
+
does not grant execution permission; the actual call is authorized independently.
|
|
637
|
+
|
|
638
|
+
### Tool rate limits
|
|
639
|
+
|
|
640
|
+
The same frozen configuration accepts `[tool.pybun.mcp.rate_limit]` (or
|
|
641
|
+
`[mcp.rate_limit]` in `.pybun/config.toml`):
|
|
642
|
+
|
|
643
|
+
```toml
|
|
644
|
+
[tool.pybun.mcp.rate_limit]
|
|
645
|
+
calls_per_minute = 60 # all calls in one stdio session
|
|
646
|
+
global_calls_per_minute = 600 # all sessions in this process/configuration
|
|
647
|
+
pybun_run_per_minute = 10
|
|
648
|
+
pybun_install_per_hour = 20
|
|
649
|
+
|
|
650
|
+
[tool.pybun.mcp.rate_limit.per_tool.pybun_lock]
|
|
651
|
+
calls = 5
|
|
652
|
+
window_secs = 60
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
These are token buckets: each starts with a full window's burst and refills
|
|
656
|
+
continuously using monotonic time. All applicable buckets are checked and debited
|
|
657
|
+
atomically after policy authorization, before filesystem snapshots, backend work
|
|
658
|
+
or subprocesses. Resource aliases share the corresponding tool's bucket. A denied
|
|
659
|
+
call debits no bucket; cancelling an admitted call retains its debit. Rejections
|
|
660
|
+
return `mcp.rate_limit_exceeded`, positive `retry_after_ms`, scope and applicable
|
|
661
|
+
limits in `rate_limit_applied`, and write a security audit event without invoking
|
|
662
|
+
the tool or producing its success receipt. Admitted responses expose their limits.
|
|
663
|
+
|
|
664
|
+
One stdio server owns one generated session UUID for its lifetime; client IDs and
|
|
665
|
+
arguments cannot change it. Global/per-tool quotas are shared by server instances
|
|
666
|
+
in the same process with the same canonical startup directory and frozen rate
|
|
667
|
+
configuration. Separate processes and restarts do not share quota state. State
|
|
668
|
+
is bounded to 64 configurations and 512 sessions per configuration; live entries
|
|
669
|
+
are never evicted to reset a quota. Idle sessions expire after two minutes and
|
|
670
|
+
configurations after twice their longest tool window (at least two hours).
|
|
671
|
+
Capacity exhaustion returns a retry hint. Configuration rejects unknown tool names
|
|
672
|
+
and fields, zero or more than 1,000,000 calls, and windows outside 1–86,400 seconds.
|
|
673
|
+
|
|
516
674
|
## JSON output examples
|
|
517
675
|
|
|
518
676
|
All commands support the `--format=json` option (schema v1). Examples:
|
|
@@ -686,3 +844,65 @@ Environment override: `PYBUN_TELEMETRY=0|1`
|
|
|
686
844
|
## License
|
|
687
845
|
|
|
688
846
|
MIT
|
|
847
|
+
|
|
848
|
+
### Local execution history
|
|
849
|
+
|
|
850
|
+
`pybun history --since 20m --format=json` queries CLI receipts for `run`, `test`,
|
|
851
|
+
`install`, `add`, and `remove`, including failed invocations. Receipts are appended
|
|
852
|
+
under `PYBUN_HOME/history.jsonl` (default `~/.cache/pybun/history.jsonl`), with a
|
|
853
|
+
private file mode on Unix and a file lock for concurrent writers. Existing files
|
|
854
|
+
with group/other permissions are rejected for appends; restrict them to mode 600. Set
|
|
855
|
+
`PYBUN_HISTORY=0` to disable recording. History queries never record themselves;
|
|
856
|
+
recording failures warn without changing the command's exit code.
|
|
857
|
+
|
|
858
|
+
Records contain timestamps, duration, exit code, lock digest when available,
|
|
859
|
+
backend identity, Python version/ABI, and known cache/policy/profile evidence.
|
|
860
|
+
Python observations identify whether they describe the execution interpreter or
|
|
861
|
+
the selected environment observed after the command; unavailable evidence is
|
|
862
|
+
`null`. Daemon use is false for the current direct execution paths. Inline code
|
|
863
|
+
and credential option values are removed, and the existing output redaction rules
|
|
864
|
+
apply before persistence and on reads. Program output and environment variables
|
|
865
|
+
are never stored. Arbitrary positional secrets cannot be inferred; disable
|
|
866
|
+
history when arguments contain such values. Queries accept positive `s`, `m`,
|
|
867
|
+
`h`, or `d` durations, report corrupt/partial lines without echoing them, and
|
|
868
|
+
reject files over 32 MiB; archive the local log when needed.
|
|
869
|
+
|
|
870
|
+
### Comparing captured environments
|
|
871
|
+
|
|
872
|
+
`pybun why <package> --format=json` explains every known declaration path to a
|
|
873
|
+
locked distribution. MCP `pybun_why` takes a `package` string and uses the same
|
|
874
|
+
service. The uv backend reads frozen, offline `uv workspace metadata`; it does
|
|
875
|
+
not resolve, install, or synchronize an environment. Paths retain markers and
|
|
876
|
+
classify direct, transitive, extra, dependency-group, and workspace provenance.
|
|
877
|
+
They describe lock alternatives rather than the currently active environment.
|
|
878
|
+
Results include backend/version, lock digest, `complete`, and diagnostics. An
|
|
879
|
+
unreachable or unavailable locked variant is explicitly `unknown`. Native
|
|
880
|
+
provenance uses current declarations with its lock and always reports partial
|
|
881
|
+
evidence. Traversal is bounded (10,000 nodes, 100,000 expansions, depth 256,
|
|
882
|
+
1,000 paths); reaching a bound also reports partial evidence.
|
|
883
|
+
|
|
884
|
+
`pybun environment diff before.json after.json --format=json` compares two JSON
|
|
885
|
+
captures, raw execution receipts, or JSON command envelopes. The MCP equivalent is
|
|
886
|
+
`pybun_environment_diff` with `before` and `after` objects; both use the same
|
|
887
|
+
read-only service. To compare CLI history entries, save the chosen `records[]`
|
|
888
|
+
objects from `pybun history --format=json` as the input files. Each input file is
|
|
889
|
+
limited to 4 MiB.
|
|
890
|
+
|
|
891
|
+
The response reports deterministic `changes` with reason codes for Python,
|
|
892
|
+
backend, lock, package, policy, profile and project evidence. `installed_packages`
|
|
893
|
+
describes interpreter-site inventory; `packages` separately describes a backend's
|
|
894
|
+
resolved lock inventory, which does not prove installation. History captures
|
|
895
|
+
standard isolated-venv site packages by reading distribution metadata without
|
|
896
|
+
importing project packages or running site hooks. System Python and venvs that
|
|
897
|
+
include system/user sites leave the installed inventory unavailable. Executable
|
|
898
|
+
digests are captured when readable; wheel artifact hashes remain unavailable
|
|
899
|
+
unless supplied by a capture.
|
|
900
|
+
|
|
901
|
+
Missing, masked and explicitly unknown values appear in `unavailable` and warning
|
|
902
|
+
diagnostics. They do not establish equality or a runtime state change. An identical
|
|
903
|
+
partial capture has an empty `changes` array while still reporting missing evidence.
|
|
904
|
+
Array ordering and normalized package spelling do not create differences. Only
|
|
905
|
+
typed, allowlisted metadata is compared; unrestricted environment variables and
|
|
906
|
+
unknown configuration keys are excluded, and existing redaction applies before
|
|
907
|
+
comparison. `causality: "not_inferred"` means the result describes recorded state
|
|
908
|
+
changes and never claims that they caused a program's output to change.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|