vallum 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore +2 -0
- package/CHANGELOG.md +66 -0
- package/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +349 -0
- package/binary-install.js +348 -0
- package/binary.js +124 -0
- package/install.js +4 -0
- package/npm-shrinkwrap.json +52 -0
- package/package.json +105 -0
- package/run-vallum.js +4 -0
package/.gitignore
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.3.0]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `vallum doctor` — install/health self-check that validates the config file,
|
|
12
|
+
flags unknown `[optimizer] disabled` names, reports whether the Claude Code
|
|
13
|
+
hook is installed, checks that a `vallum` binary is on `PATH`, and probes the
|
|
14
|
+
log directory for writability. Exits non-zero only on a hard failure.
|
|
15
|
+
- `kubectl get` optimizer — collapses runs of healthy (`Running`/`Completed`)
|
|
16
|
+
resource rows while keeping the header and any pod in a problem state
|
|
17
|
+
(`CrashLoopBackOff`, `Pending`, `Evicted`, …).
|
|
18
|
+
- `terraform plan|apply` optimizer — collapses state-refresh chatter and
|
|
19
|
+
attribute-diff bodies while keeping per-resource action headers, the
|
|
20
|
+
`Plan:`/`Apply complete!` summary, and errors.
|
|
21
|
+
- Expanded secret-format coverage: GitLab (`glpat-`), SendGrid (`SG.`),
|
|
22
|
+
Twilio (`SK…`), npm (`npm_`), PyPI (`pypi-`), Hugging Face (`hf_`), OpenAI
|
|
23
|
+
project keys (`sk-proj-`), and bare (non-`Bearer`) JWTs.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Documented a minimum supported Rust version (`rust-version = "1.85"`, raised
|
|
27
|
+
from 1.82 to track the `clap` 4.6 edition-2024 floor) and enforce it with a
|
|
28
|
+
dedicated, `--locked` CI job.
|
|
29
|
+
|
|
30
|
+
### Security
|
|
31
|
+
- New scheduled `cargo audit` GitHub Actions workflow that fails on known
|
|
32
|
+
advisories in the dependency tree. Granted it `checks: write` so it can post
|
|
33
|
+
results instead of erroring on the check-run API.
|
|
34
|
+
- Bumped the `anyhow` dev-dependency to 1.0.103, clearing RUSTSEC-2026-0190
|
|
35
|
+
(unsoundness in `Error::downcast_mut`).
|
|
36
|
+
|
|
37
|
+
### Distribution
|
|
38
|
+
- Prebuilt binaries for macOS (Intel + ARM) and Linux (x86_64 + aarch64, musl
|
|
39
|
+
static) published on tagged releases via `dist`, with shell, Homebrew,
|
|
40
|
+
`cargo install`, and npm installers, SHA-256 checksums, and GitHub build
|
|
41
|
+
provenance attestations.
|
|
42
|
+
|
|
43
|
+
## [0.2.0]
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
- ANSI stripping, whitespace collapse, token metrics, and `vallum stats`.
|
|
47
|
+
- Per-command optimizer framework with optimizers for `git status`/`diff`/`log`,
|
|
48
|
+
`cargo`, `pytest`, `npm`, `docker`, `go test`, `make`, `rg`/`grep`, and
|
|
49
|
+
`ls`/`find`/`fd`/`tree`.
|
|
50
|
+
- Concurrent bounded capture (byte cap, timeout, inherited stdin),
|
|
51
|
+
context-preserving truncation, and an optional `--features bpe` token counter.
|
|
52
|
+
- Claude Code integration: `install-hook`/`uninstall-hook`, the `vallum hook`
|
|
53
|
+
handler, `config show`/`config init`, shell completions, and `--tee` live log.
|
|
54
|
+
- Security pipeline: multilingual prompt-injection neutralization (with
|
|
55
|
+
invisible/bidi stripping and homoglyph folding, plus `--strict` fail-closed
|
|
56
|
+
mode), known-format secret redaction with context-gated entropy detection,
|
|
57
|
+
untrusted-output wrapping with marker defang, and private-by-default logging.
|
|
58
|
+
|
|
59
|
+
## [0.1.0]
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
- MVP: execute a command through the proxy, truncate, scrub secrets, and audit.
|
|
63
|
+
|
|
64
|
+
[0.3.0]: https://github.com/kahramanemir/Vallum/releases/tag/v0.3.0
|
|
65
|
+
[0.2.0]: https://github.com/kahramanemir/Vallum/releases/tag/v0.2.0
|
|
66
|
+
[0.1.0]: https://github.com/kahramanemir/Vallum/releases/tag/v0.1.0
|
package/LICENSE-APACHE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for describing the origin of the Work and
|
|
141
|
+
reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Support. While redistributing the Work or
|
|
166
|
+
Derivative Works thereof, You may choose to offer, and charge a
|
|
167
|
+
fee for, acceptance of support, warranty, indemnity, or other
|
|
168
|
+
liability obligations and/or rights consistent with this License.
|
|
169
|
+
However, in accepting such obligations, You may act only on Your
|
|
170
|
+
own behalf and on Your sole responsibility, not on behalf of any
|
|
171
|
+
other Contributor, and only if You agree to indemnify, defend,
|
|
172
|
+
and hold each Contributor harmless for any liability incurred by,
|
|
173
|
+
or claims asserted against, such Contributor by reason of your
|
|
174
|
+
accepting any such warranty or support.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 Emir Kahraman
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
200
|
+
implied. See the License for the specific language governing
|
|
201
|
+
permissions and limitations under the License.
|
package/LICENSE-MIT
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Emir Kahraman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# Vallum
|
|
2
|
+
|
|
3
|
+
*A security boundary between AI coding agents (Claude Code, Cursor, etc.) and your shell — secret redaction, prompt-injection defense, ANSI stripping, and command auditing in a single Rust binary.*
|
|
4
|
+
|
|
5
|
+
[](https://github.com/kahramanemir/Vallum/actions/workflows/ci.yml)
|
|
6
|
+
[](https://crates.io/crates/vallum)
|
|
7
|
+
[](#license)
|
|
8
|
+
|
|
9
|
+
A Rust CLI proxy that sits between AI agents and your shell as a **security boundary**. When an agent runs a command, Vallum redacts secrets, neutralizes prompt-injection attempts, wraps the result as untrusted data, preserves the child exit code, and audits everything — so what reaches the model is exactly what you intend it to see. As a side benefit, it strips ANSI noise and compresses long output, which also saves tokens.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Why
|
|
14
|
+
|
|
15
|
+
When an AI agent runs shell commands on your behalf, the command output flows straight into the model's context. That output is **untrusted input**, and it creates three problems:
|
|
16
|
+
|
|
17
|
+
- It may contain **secrets** — API keys, tokens, credentials — that get forwarded to the model (and possibly logged by it).
|
|
18
|
+
- It may contain **adversarial text** — log lines, scraped pages, or error messages crafted to hijack the agent ("ignore previous instructions…").
|
|
19
|
+
- It is **unstructured and noisy**, burying the relevant signal and inflating token usage.
|
|
20
|
+
|
|
21
|
+
Vallum is a single binary that puts a controlled boundary between that output and the model.
|
|
22
|
+
|
|
23
|
+
> **Scope of the guarantees.** Secret redaction and injection neutralization are **best-effort, pattern-based** defenses. They raise the cost of an attack and catch common cases; they are not a substitute for treating all terminal output as untrusted. The untrusted-output wrapper is the durable control — keep your agent prompted to respect it.
|
|
24
|
+
|
|
25
|
+
## Pipeline
|
|
26
|
+
|
|
27
|
+
```mermaid
|
|
28
|
+
flowchart LR
|
|
29
|
+
A[AI Agent] -->|vallum run cmd| B[Executor]
|
|
30
|
+
B --> C[ANSI strip]
|
|
31
|
+
C --> D{Output small?}
|
|
32
|
+
D -->|yes| W[Whitespace collapse]
|
|
33
|
+
D -->|no| E{Known command?}
|
|
34
|
+
E -->|yes| F[Optimizer]
|
|
35
|
+
E -->|no| G[Whitespace collapse + Truncate]
|
|
36
|
+
F --> G
|
|
37
|
+
W --> H[Scrubber]
|
|
38
|
+
G --> H
|
|
39
|
+
H --> I[AI Agent]
|
|
40
|
+
B -. raw, opt-in .-> J[(~/.vallum/logs/raw.local.log)]
|
|
41
|
+
H -. sanitized .-> K[(~/.vallum/logs/sanitized.ai.log)]
|
|
42
|
+
B -.->|tokens_before| L[(~/.vallum/stats.jsonl)]
|
|
43
|
+
H -.->|tokens_after| L
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Each command flows through these stages:
|
|
47
|
+
|
|
48
|
+
1. **Execute** — `stdout` and `stderr` are captured concurrently and merged in arrival order. Capture is bounded by a byte cap and a timeout (see Configuration). `stdin` is inherited so interactive commands work.
|
|
49
|
+
2. **ANSI strip** — color and cursor-control escapes are removed.
|
|
50
|
+
3. **Short-circuit** — if the output is below `min_optimize_tokens`, the optimize and truncate stages are skipped (no point summarizing a few lines). The security stages below always run.
|
|
51
|
+
4. **Optimize** — if a registered `CommandOptimizer` matches (e.g. `git status`, `cargo test`, `pytest`, `npm test`), it produces a compressed view; otherwise the input passes through.
|
|
52
|
+
5. **Whitespace collapse** — runs of three or more blank lines collapse to one; trailing spaces are stripped.
|
|
53
|
+
6. **Truncate** — head/tail windows are preserved; important lines (errors, panics, failures) are kept **in place with surrounding context**, and ordinary gaps are elided.
|
|
54
|
+
7. **Scrub** — API tokens (OpenAI, Anthropic, GitHub, GitLab, Slack, AWS, Google, Stripe, SendGrid, Twilio, npm, PyPI, Hugging Face), bearer/bare JWTs, connection-string passwords, and PEM private keys are redacted; known injection phrases are neutralized.
|
|
55
|
+
8. **Wrap** — output is enclosed in `[UNTRUSTED TERMINAL OUTPUT]` markers; any forged markers inside the content are defanged so output can't break out of the wrapper.
|
|
56
|
+
9. **Audit + Metrics** — the sanitized output is written under `~/.vallum/logs/` (raw logging is opt-in), and a per-command stats record is appended to `~/.vallum/stats.jsonl`.
|
|
57
|
+
|
|
58
|
+
## Security model
|
|
59
|
+
|
|
60
|
+
Vallum applies four mechanism families to every command, in order:
|
|
61
|
+
prompt-injection neutralization (multilingual, with invisible/bidi character
|
|
62
|
+
stripping and a homoglyph-folded detection shadow; opt-in `--strict`
|
|
63
|
+
fail-closed mode), secret redaction (known-format patterns plus context-gated
|
|
64
|
+
entropy detection), untrusted-output wrapping with marker defang, and
|
|
65
|
+
private-by-default logging (raw log opt-in, `0600` permissions).
|
|
66
|
+
|
|
67
|
+
**Full threat model:** see [SECURITY.md](SECURITY.md) — what is protected,
|
|
68
|
+
by which mechanism, at what strength, and what is explicitly **not**
|
|
69
|
+
guaranteed.
|
|
70
|
+
|
|
71
|
+
## Built-in Optimizers
|
|
72
|
+
|
|
73
|
+
- `git status`: summarizes large working-tree sections while keeping branch state and representative file entries
|
|
74
|
+
- `git diff` / `git log`: collapse large unchanged-context runs / long commit bodies while keeping headers, hunks, and changed lines
|
|
75
|
+
- `cargo build|test|check|clippy|run`: collapses compile/download noise and preserves summaries, failures, and diagnostics
|
|
76
|
+
- `pytest` and `python -m pytest`: hides progress-dot spam while keeping collection, failure, and summary sections
|
|
77
|
+
- `npm test|install|ci|run`: collapses repeated `PASS` and warning lines while preserving result summaries
|
|
78
|
+
- `docker build|compose`: collapse layer/step progress while keeping step headers, errors, and the final result
|
|
79
|
+
- `go test`: hide `=== RUN`/`--- PASS` spam while keeping failures and the summary
|
|
80
|
+
- `make`: surface errors/warnings while collapsing ordinary build noise
|
|
81
|
+
- `kubectl get`: collapse runs of healthy (`Running`/`Completed`) resources while keeping the header and any pod in a problem state (`CrashLoopBackOff`, `Pending`, `Evicted`, …)
|
|
82
|
+
- `terraform plan|apply`: collapse state-refresh chatter and attribute-diff bodies while keeping resource action headers, the `Plan:`/`Apply complete!` summary, and errors
|
|
83
|
+
- `rg` / `grep` (also `egrep`/`fgrep`): group matches by file, keep the first few per file, and summarize the rest with per-file and total counts
|
|
84
|
+
- `ls` / `find` / `fd` / `tree`: keep the leading entries and summarize the rest (with a top-directories breakdown for path lists); error lines are always preserved
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
Vallum looks for `~/.vallum/config.toml` by default. For testing or per-project overrides, point `VALLUM_CONFIG` at a different file.
|
|
89
|
+
|
|
90
|
+
```toml
|
|
91
|
+
[audit]
|
|
92
|
+
log_dir = "/tmp/vallum-logs"
|
|
93
|
+
raw_enabled = false # raw, unredacted logging is opt-in
|
|
94
|
+
sanitized_enabled = true
|
|
95
|
+
|
|
96
|
+
[pipeline]
|
|
97
|
+
head_lines = 20
|
|
98
|
+
tail_lines = 20
|
|
99
|
+
min_optimize_tokens = 50 # skip optimize/truncate below this token estimate
|
|
100
|
+
max_output_bytes = 10485760 # 10 MiB capture cap; excess is dropped with a marker
|
|
101
|
+
timeout_secs = 300 # kill the child after N seconds (0 = disabled)
|
|
102
|
+
max_line_length = 2000 # truncate single lines longer than this (0 disables)
|
|
103
|
+
|
|
104
|
+
[optimizer]
|
|
105
|
+
disabled = [] # optimizer names to turn off; all on by default
|
|
106
|
+
|
|
107
|
+
[scrubber]
|
|
108
|
+
entropy = true # context-gated entropy redaction of credential-ish values
|
|
109
|
+
normalize = true # strip invisible/bidi chars; fold homoglyphs for injection matching
|
|
110
|
+
extra_secret_patterns = [
|
|
111
|
+
{ pattern = "token-[0-9]+", replacement = "token-***" }
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
[security]
|
|
115
|
+
strict = false # block the entire output when a prompt injection is detected
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Supported settings:
|
|
119
|
+
|
|
120
|
+
- `audit.log_dir`: audit log directory override
|
|
121
|
+
- `audit.raw_enabled`: enable raw (unredacted) terminal logs — **default `false`**
|
|
122
|
+
- `audit.sanitized_enabled`: enable or disable sanitized logs
|
|
123
|
+
- `pipeline.head_lines` / `pipeline.tail_lines`: truncation window
|
|
124
|
+
- `pipeline.min_optimize_tokens`: outputs below this estimate skip optimize/truncate
|
|
125
|
+
- `pipeline.max_output_bytes`: maximum bytes captured from a command (default 10 MiB)
|
|
126
|
+
- `pipeline.timeout_secs`: command timeout in seconds; `0` disables it (default 300)
|
|
127
|
+
- `optimizer.disabled`: list of optimizer names to disable (git_status, git_diff, git_log, cargo, pytest, npm, docker, go_test, make, kubectl, terraform, grep, file_list) — default none
|
|
128
|
+
- `pipeline.max_line_length`: cap individual line length; longer lines are truncated mid-line with an elision marker — default 2000, `0` disables
|
|
129
|
+
- `scrubber.extra_secret_patterns`: extra regex-based redaction rules
|
|
130
|
+
- `scrubber.entropy`: context-gated entropy redaction of credential-ish assignment values — **default `true`**
|
|
131
|
+
- `scrubber.normalize`: strip invisible/bidi characters and fold homoglyphs for injection matching — **default `true`**
|
|
132
|
+
- `security.strict`: when `true` (or `--strict`), the output is replaced with `[OUTPUT BLOCKED: prompt injection detected]` if any injection is detected — **default `false`**
|
|
133
|
+
|
|
134
|
+
## Install
|
|
135
|
+
|
|
136
|
+
**Shell (macOS + Linux):**
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/kahramanemir/Vallum/releases/latest/download/vallum-installer.sh | sh
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Homebrew:**
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
brew install kahramanemir/homebrew-tap/vallum
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Cargo:**
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
cargo install vallum # heuristic token counts (default)
|
|
152
|
+
cargo install vallum --features bpe # exact BPE token counts (adds tiktoken-rs)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**npm:**
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npm install -g vallum
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Prebuilt binaries** for macOS (Intel + ARM) and Linux (x86_64 + aarch64) are
|
|
162
|
+
attached to every [GitHub Release](https://github.com/kahramanemir/Vallum/releases),
|
|
163
|
+
with SHA-256 checksums and build-provenance attestations. Verify a download with:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
gh attestation verify ./vallum --repo kahramanemir/Vallum
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Build from source
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
cargo build --release # default: dependency-free heuristic token counts
|
|
173
|
+
cargo build --release --features bpe # exact BPE token counts (adds tiktoken-rs)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The binary lands at `target/release/vallum`.
|
|
177
|
+
|
|
178
|
+
## Usage
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
vallum run <command> [args...] # run a command through the proxy
|
|
182
|
+
vallum run --json <command> ... # emit structured JSON
|
|
183
|
+
vallum run --strict <command> ... # block output if a prompt injection is detected
|
|
184
|
+
vallum run --tee <command> ... # also append raw output to ~/.vallum/live.log
|
|
185
|
+
vallum stats # show cumulative token savings
|
|
186
|
+
vallum stats --reset # delete collected stats
|
|
187
|
+
|
|
188
|
+
# Integration & UX
|
|
189
|
+
vallum install-hook # register vallum in ~/.claude/settings.json
|
|
190
|
+
vallum install-hook --project # register in <cwd>/.claude/settings.json
|
|
191
|
+
vallum uninstall-hook # remove the vallum hook entry
|
|
192
|
+
vallum hook # internal: invoked by Claude Code (don't run directly)
|
|
193
|
+
vallum config show # print effective merged config as TOML
|
|
194
|
+
vallum config init [--force] # scaffold ~/.vallum/config.toml
|
|
195
|
+
vallum doctor # self-check: config, hook, PATH, log dir
|
|
196
|
+
vallum completions <bash|zsh|fish|elvish|powershell> > completions/_vallum
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Examples:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
vallum run ls -la
|
|
203
|
+
vallum run cargo test
|
|
204
|
+
vallum run git status
|
|
205
|
+
vallum run pytest
|
|
206
|
+
vallum run npm test
|
|
207
|
+
vallum run sh -- -c 'exit 7' # preserves the child exit code
|
|
208
|
+
vallum run --json printf "hello\n"
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Example JSON output:
|
|
212
|
+
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"command": "printf",
|
|
216
|
+
"args": ["hello\\n"],
|
|
217
|
+
"exit_code": 0,
|
|
218
|
+
"optimizer": null,
|
|
219
|
+
"tokens_before": 1,
|
|
220
|
+
"tokens_after": 18,
|
|
221
|
+
"sanitized_output": "[UNTRUSTED TERMINAL OUTPUT START]\nhello\n[UNTRUSTED TERMINAL OUTPUT END]\n"
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Note how a tiny output ends up *larger* after wrapping: the security wrapper has a fixed cost, and on short commands that cost dominates. Token savings show up on the large, noisy outputs (builds, test runs, big diffs) — see below.
|
|
226
|
+
|
|
227
|
+
### Exit codes
|
|
228
|
+
|
|
229
|
+
- The child's own exit code is propagated as Vallum's exit code on success.
|
|
230
|
+
- Vallum-level failures (bad config, executor spawn error, JSON serialization error) exit **`125`** — the `env(1)` "command not invoked" convention — so they are distinguishable from the child's real exit 1.
|
|
231
|
+
|
|
232
|
+
## Claude Code integration
|
|
233
|
+
|
|
234
|
+
`vallum install-hook` writes a `PreToolUse` entry into `~/.claude/settings.json` (default, user-level) or `<cwd>/.claude/settings.json` (`--project`). A timestamped `.bak-<unix_ts>` backup of the settings file is written before any modification. The command is idempotent — re-running it without `--force` is a no-op if the entry already exists; `--force` replaces an existing entry.
|
|
235
|
+
|
|
236
|
+
Once installed, Claude Code invokes `vallum hook` before every Bash tool call. The hook rewrites the command to `vallum run -- bash -c '<original>'` so the full Vallum pipeline (capture, ANSI strip, optimize, scrub, wrap) runs on every shell invocation without any change to how you or the agent writes commands. Because the hook wraps commands as `bash -c '<original>'`, Vallum unwraps simple scripts (no pipes, redirects, quoting, or other shell metacharacters) before optimizer matching, so `bash -c 'git status'` still hits the `git_status` optimizer; complex scripts fall back to generic compression. Known TUI programs (`vim`, `vi`, `nano`, `less`, `more`, `top`, `htop`, `tmux`, `screen`) are skipped because Vallum captures stdout and would break their TTY requirements. Commands already starting with `vallum` are skipped for idempotency.
|
|
237
|
+
|
|
238
|
+
To remove the hook, run `vallum uninstall-hook` — it removes only the Vallum entry, leaving the rest of your settings file untouched.
|
|
239
|
+
|
|
240
|
+
**Live progress.** `vallum run --tee` appends the child's raw stdout/stderr to `~/.vallum/live.log` as lines arrive. Watch it from a side terminal with `tail -f ~/.vallum/live.log`. The tee target is a private file (`0600`), not a stream the agent ever reads — the agent's input is still the wrapped, scrubbed pipeline output on stdout. Tee is best-effort: if the file can't be opened or written, the command runs normally without it.
|
|
241
|
+
|
|
242
|
+
## Measuring savings
|
|
243
|
+
|
|
244
|
+
Every `vallum run` appends one JSON record to `~/.vallum/stats.jsonl` with raw and sanitized token estimates. Counting goes through a pluggable `TokenEstimator`; the default is a dependency-free heuristic (word runs + symbols) that tracks BPE better than a flat chars/4 ratio. `vallum stats` aggregates the file. Build with `--features bpe` to count tokens with an exact `tiktoken` (o200k_base) tokenizer instead of the default dependency-free heuristic; it is an OpenAI-family approximation of Claude's tokenizer.
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
Vallum — Token savings report
|
|
248
|
+
─────────────────────────────────────────
|
|
249
|
+
Commands run: 142
|
|
250
|
+
Tokens (raw): 58,420
|
|
251
|
+
Tokens (sanitized): 11,205
|
|
252
|
+
Saved: 47,215 (80.8%)
|
|
253
|
+
|
|
254
|
+
Top savings by command
|
|
255
|
+
─────────────────────────────────────────
|
|
256
|
+
cargo build 18,940 saved (94%)
|
|
257
|
+
git status 12,103 saved (88%)
|
|
258
|
+
npm install 8,442 saved (76%)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Reproducing the savings
|
|
262
|
+
|
|
263
|
+
Run `cargo bench` to time the full pipeline against seven committed fixtures (`git status`, `cargo build`, `pytest`, `npm install`, a minified blob, an `rg` match list, a `find` file list) and print a raw-vs-sanitized token table. Fixtures live in `benches/fixtures/` and are versioned with the repo, so the savings figures are reproducible from a clean checkout. The bench also prints a summary table to stderr after all criterion measurements complete, showing each fixture's before/after token counts.
|
|
264
|
+
|
|
265
|
+
## Tests
|
|
266
|
+
|
|
267
|
+
**Property tests.** The scrubber, truncator, ansi, whitespace, and optimizer modules carry inline `proptest` invariants (no-panic, structural bounds, idempotency) that run under the normal `cargo test`.
|
|
268
|
+
|
|
269
|
+
## Modules
|
|
270
|
+
|
|
271
|
+
| File | Responsibility |
|
|
272
|
+
| ----------------------------- | ---------------------------------------------------- |
|
|
273
|
+
| `src/cli.rs` | Argument parsing (`run`, `stats`, `hook`, `install-hook`/`uninstall-hook`, `config`, `completions`) |
|
|
274
|
+
| `src/config.rs` | Config loading, defaults, and validation |
|
|
275
|
+
| `src/executor.rs` | Concurrent capture with byte cap, timeout, stdin; optional tee to `~/.vallum/live.log` |
|
|
276
|
+
| `src/ansi.rs` | Stripping ANSI escape sequences |
|
|
277
|
+
| `src/whitespace.rs` | Collapsing blank-line runs, stripping trailing space |
|
|
278
|
+
| `src/optimizer/mod.rs` | `CommandOptimizer` trait + dispatch registry |
|
|
279
|
+
| `src/optimizer/cargo.rs` | Summary optimizer for noisy `cargo` output |
|
|
280
|
+
| `src/optimizer/docker.rs` | Summary optimizer for `docker build`/`compose` output |
|
|
281
|
+
| `src/optimizer/file_list.rs` | Entry-capping optimizer for `ls`/`find`/`fd`/`tree` |
|
|
282
|
+
| `src/optimizer/git_diff.rs` | Summary optimizer for `git diff` output |
|
|
283
|
+
| `src/optimizer/git_log.rs` | Summary optimizer for `git log` output |
|
|
284
|
+
| `src/optimizer/git_status.rs` | Summary optimizer for `git status` output |
|
|
285
|
+
| `src/optimizer/go_test.rs` | Summary optimizer for `go test` output |
|
|
286
|
+
| `src/optimizer/grep.rs` | Match-grouping optimizer for `rg`/`grep` output |
|
|
287
|
+
| `src/optimizer/kubectl.rs` | Healthy-row collapsing optimizer for `kubectl get` output |
|
|
288
|
+
| `src/optimizer/make.rs` | Summary optimizer for `make` output |
|
|
289
|
+
| `src/optimizer/npm.rs` | Summary optimizer for noisy `npm` output |
|
|
290
|
+
| `src/optimizer/pytest.rs` | Summary optimizer for noisy `pytest` output |
|
|
291
|
+
| `src/optimizer/terraform.rs` | Summary optimizer for `terraform plan`/`apply` output |
|
|
292
|
+
| `src/truncator.rs` | Context-preserving head/tail truncation |
|
|
293
|
+
| `src/scrubber/mod.rs` | Scrub pipeline: `sanitize`/`redact` orchestration + wrapper |
|
|
294
|
+
| `src/scrubber/secrets.rs` | Known-format secret redaction patterns |
|
|
295
|
+
| `src/scrubber/entropy.rs` | Context-gated entropy secret detection |
|
|
296
|
+
| `src/scrubber/injection.rs` | Prompt-injection neutralization |
|
|
297
|
+
| `src/scrubber/normalize.rs` | Invisible-char strip + homoglyph detection shadow |
|
|
298
|
+
| `src/scrubber/markers.rs` | Untrusted-output marker defang |
|
|
299
|
+
| `src/tokenizer.rs` | Pluggable `TokenEstimator` + heuristic default |
|
|
300
|
+
| `src/fsutil.rs` | Private (0600) append-file helper |
|
|
301
|
+
| `src/audit.rs` | Append-only log writer |
|
|
302
|
+
| `src/metrics.rs` | Token estimation + JSONL stats writer |
|
|
303
|
+
| `src/stats.rs` | `vallum stats` aggregation and reporting |
|
|
304
|
+
| `src/hook.rs` | Claude Code PreToolUse handler: rewrites Bash calls to `vallum run` |
|
|
305
|
+
| `src/install_hook.rs` | `install-hook`/`uninstall-hook`: read-modify-write of Claude Code settings.json |
|
|
306
|
+
| `src/doctor.rs` | `vallum doctor`: install/health self-checks (config, hook, PATH, log dir) |
|
|
307
|
+
| `src/main.rs` | Pipeline wiring |
|
|
308
|
+
| `src/lib.rs` | Library surface — re-exports modules so integration tests can exercise internals |
|
|
309
|
+
|
|
310
|
+
## Roadmap
|
|
311
|
+
|
|
312
|
+
- [x] v0.1 — MVP: execute, truncate, scrub, audit
|
|
313
|
+
- [x] v0.2 — ANSI strip, whitespace collapse, token metrics, per-command optimizer framework, `vallum stats`
|
|
314
|
+
- [x] Post-v0.2 hardening — exit-code propagation, structured JSON output, configurable pipeline, cargo/pytest/npm optimizers
|
|
315
|
+
- [x] Security sweep — concurrent bounded capture (cap + timeout + stdin), context-preserving truncation, broadened injection neutralization, marker anti-spoofing, raw-logs-off-by-default with `0600` perms, small-output short-circuit, pluggable token estimator
|
|
316
|
+
- [x] Sub-project B — broader command coverage (git diff, git log, docker, go test, make), optimizer toggles (`[optimizer] disabled`), long-line truncation (`pipeline.max_line_length`), optional BPE token counting (`--features bpe`)
|
|
317
|
+
- [x] Sub-project C — integration/UX: `install-hook`/`uninstall-hook` (Claude Code PreToolUse), `vallum hook` handler, `config show`/`config init`, `vallum completions <shell>`, exit-125 convention
|
|
318
|
+
- [x] Sub-project D — live-tee (`vallum run --tee`, `~/.vallum/live.log`); PTY/streaming proper descoped because the hook skip-list (sub-project C) removed the urgency
|
|
319
|
+
- [x] Sub-project E — maturity: `proptest` invariants across scrubber/truncator/ansi/whitespace/optimizer modules; `criterion` benchmark harness with five versioned fixtures (`benches/fixtures/`); savings figures reproducible from a clean checkout
|
|
320
|
+
- [x] grep/file_list optimizers + hook-mode dispatch fix — `bash -c` unwrap so optimizers fire via the Claude Code hook; `rg`/`grep` match grouping; `ls`/`find`/`fd`/`tree` entry capping; two new bench fixtures (seven total)
|
|
321
|
+
- [x] Context-gated entropy secret detection — credential-ish assignment values with high Shannon entropy are masked; bare tokens (commit SHAs, UUIDs) structurally exempt; `[scrubber] entropy` flag (default on)
|
|
322
|
+
- [x] Injection precision tuning — reveal-family requires a possessive or system-directed object in all five languages; `System:`/`Assistant:` turn lines get a natural-language veto so log lines pass; entropy tokenizer captures separator runs (`key== "<value>"`); security corpus grown to 20 injections / 18 benign samples
|
|
323
|
+
- [x] Sub-project I — injection input normalization (strip invisible/bidi; NFKC + confusable-folded detection shadow; no-space ignore-family; `scrubber.normalize` flag)
|
|
324
|
+
- [x] Sub-project J — scrub-stage hardening: injection scan before secret masking (closes the secret-eats-trigger gap), reveal-family no-space detection in five languages, config extra-pattern compile-once (`CompiledRule`)
|
|
325
|
+
- [x] Sub-project K — broader infra/optimizer coverage: `kubectl get` (collapse healthy resource rows, keep problem-state pods) and `terraform plan|apply` (collapse refresh chatter + attribute diffs, keep action headers/summary/errors); expanded secret-format coverage (GitLab, SendGrid, Twilio, npm, PyPI, Hugging Face, OpenAI project keys, bare JWTs)
|
|
326
|
+
- [x] Sub-project L — `vallum doctor` install/health self-check: validates the config file, flags unknown `[optimizer] disabled` names, reports hook installation, checks the binary is on `PATH`, and probes log-dir writability (exit non-zero only on hard failures)
|
|
327
|
+
- [x] Sub-project M — distribution: `dist`-based tagged-release pipeline producing prebuilt binaries for macOS (Intel + ARM) and Linux (x86_64 + aarch64, musl static) with shell/Homebrew/`cargo install`/npm installers, SHA-256 checksums, and GitHub build-provenance attestations; crates.io publish on final tags; MSRV raised to 1.85 and the MSRV CI check pinned with `--locked`
|
|
328
|
+
- [ ] Deferred — Chinese-language injection, `cargo-fuzz`/libFuzzer harness, performance regression gating, Windows support (the `0600`/timeout-backed guarantees need a Windows equivalent first)
|
|
329
|
+
|
|
330
|
+
## Name
|
|
331
|
+
|
|
332
|
+
**Vallum** — Latin for the defensive embankment along Roman frontier fortifications. The thing that stands between what's inside and what's outside.
|
|
333
|
+
|
|
334
|
+
## License
|
|
335
|
+
|
|
336
|
+
Licensed under either of
|
|
337
|
+
|
|
338
|
+
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
|
|
339
|
+
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
|
|
340
|
+
|
|
341
|
+
at your option.
|
|
342
|
+
|
|
343
|
+
### Contribution
|
|
344
|
+
|
|
345
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the local workflow (fmt/clippy/test
|
|
346
|
+
gate, MSRV, how to add an optimizer or secret pattern) and [CHANGELOG.md](CHANGELOG.md)
|
|
347
|
+
for the release history.
|
|
348
|
+
|
|
349
|
+
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
const {
|
|
2
|
+
createWriteStream,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
mkdtemp,
|
|
6
|
+
rmSync,
|
|
7
|
+
} = require("fs");
|
|
8
|
+
const { join, sep } = require("path");
|
|
9
|
+
const { spawnSync } = require("child_process");
|
|
10
|
+
const { tmpdir } = require("os");
|
|
11
|
+
|
|
12
|
+
const https = require("node:https");
|
|
13
|
+
const http = require("node:http");
|
|
14
|
+
|
|
15
|
+
const tmpDir = tmpdir();
|
|
16
|
+
|
|
17
|
+
const error = (msg) => {
|
|
18
|
+
console.error(msg);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function getProxyForUrl(urlString) {
|
|
23
|
+
const url = new URL(urlString);
|
|
24
|
+
const isHttps = url.protocol === "https:";
|
|
25
|
+
|
|
26
|
+
const noProxy = process.env.NO_PROXY || process.env.no_proxy || "";
|
|
27
|
+
if (noProxy === "*") return null;
|
|
28
|
+
if (noProxy) {
|
|
29
|
+
const hostname = url.hostname.toLowerCase();
|
|
30
|
+
const noProxyList = noProxy.split(",").map((s) => s.trim().toLowerCase());
|
|
31
|
+
for (const entry of noProxyList) {
|
|
32
|
+
if (hostname === entry || hostname.endsWith("." + entry)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const proxyEnv = isHttps
|
|
39
|
+
? process.env.HTTPS_PROXY || process.env.https_proxy
|
|
40
|
+
: process.env.HTTP_PROXY || process.env.http_proxy;
|
|
41
|
+
|
|
42
|
+
if (!proxyEnv) return null;
|
|
43
|
+
|
|
44
|
+
const proxyUrl = new URL(proxyEnv);
|
|
45
|
+
|
|
46
|
+
let auth = null;
|
|
47
|
+
if (proxyUrl.username || proxyUrl.password) {
|
|
48
|
+
auth = `${proxyUrl.username}:${proxyUrl.password}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
hostname: proxyUrl.hostname,
|
|
53
|
+
port: proxyUrl.port || (proxyUrl.protocol === "https:" ? 443 : 80),
|
|
54
|
+
auth: auth,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function connectThroughProxy(proxy, target) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const headers = {};
|
|
61
|
+
if (proxy.auth) {
|
|
62
|
+
headers["Proxy-Authorization"] =
|
|
63
|
+
"Basic " + Buffer.from(proxy.auth).toString("base64");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const connectReq = http.request({
|
|
67
|
+
hostname: proxy.hostname,
|
|
68
|
+
port: proxy.port,
|
|
69
|
+
method: "CONNECT",
|
|
70
|
+
path: `${target.hostname}:${target.port || 443}`,
|
|
71
|
+
headers,
|
|
72
|
+
});
|
|
73
|
+
connectReq.on("connect", (res, socket) => {
|
|
74
|
+
if (res.statusCode === 200) {
|
|
75
|
+
resolve(socket);
|
|
76
|
+
} else {
|
|
77
|
+
reject(new Error(`Proxy CONNECT failed with status ${res.statusCode}`));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
connectReq.on("error", reject);
|
|
81
|
+
connectReq.end();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function download(urlString, maxRedirects) {
|
|
86
|
+
if (maxRedirects === undefined) maxRedirects = 5;
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
if (maxRedirects < 0) {
|
|
89
|
+
return reject(new Error("Too many redirects"));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const parsed = new URL(urlString);
|
|
93
|
+
const isHttps = parsed.protocol === "https:";
|
|
94
|
+
const mod = isHttps ? https : http;
|
|
95
|
+
const proxy = getProxyForUrl(urlString);
|
|
96
|
+
|
|
97
|
+
const doRequest = (extraOptions) => {
|
|
98
|
+
const options = Object.assign(
|
|
99
|
+
{
|
|
100
|
+
hostname: parsed.hostname,
|
|
101
|
+
port: parsed.port || (isHttps ? 443 : 80),
|
|
102
|
+
path: parsed.pathname + parsed.search,
|
|
103
|
+
method: "GET",
|
|
104
|
+
headers: { "User-Agent": "cargo-dist-npm-installer" },
|
|
105
|
+
},
|
|
106
|
+
extraOptions || {},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (proxy && !isHttps) {
|
|
110
|
+
// HTTP through HTTP proxy: request the full URL via the proxy
|
|
111
|
+
options.hostname = proxy.hostname;
|
|
112
|
+
options.port = proxy.port;
|
|
113
|
+
options.path = urlString;
|
|
114
|
+
if (proxy.auth) {
|
|
115
|
+
options.headers["Proxy-Authorization"] =
|
|
116
|
+
"Basic " + Buffer.from(proxy.auth).toString("base64");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const req = mod.request(options, (res) => {
|
|
121
|
+
if (
|
|
122
|
+
res.statusCode >= 300 &&
|
|
123
|
+
res.statusCode < 400 &&
|
|
124
|
+
res.headers.location
|
|
125
|
+
) {
|
|
126
|
+
res.resume();
|
|
127
|
+
const nextUrl = new URL(res.headers.location, urlString).toString();
|
|
128
|
+
return download(nextUrl, maxRedirects - 1).then(resolve, reject);
|
|
129
|
+
}
|
|
130
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
131
|
+
res.resume();
|
|
132
|
+
return reject(new Error(`HTTP ${res.statusCode} from ${urlString}`));
|
|
133
|
+
}
|
|
134
|
+
resolve(res);
|
|
135
|
+
});
|
|
136
|
+
req.on("error", reject);
|
|
137
|
+
req.end();
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
if (proxy && isHttps) {
|
|
141
|
+
connectThroughProxy(proxy, parsed).then(
|
|
142
|
+
(socket) => doRequest({ socket, agent: false }),
|
|
143
|
+
reject,
|
|
144
|
+
);
|
|
145
|
+
} else {
|
|
146
|
+
doRequest();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
class Package {
|
|
152
|
+
constructor(platform, name, url, filename, zipExt, binaries) {
|
|
153
|
+
let errors = [];
|
|
154
|
+
if (typeof url !== "string") {
|
|
155
|
+
errors.push("url must be a string");
|
|
156
|
+
} else {
|
|
157
|
+
try {
|
|
158
|
+
new URL(url);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
errors.push(e);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (name && typeof name !== "string") {
|
|
164
|
+
errors.push("package name must be a string");
|
|
165
|
+
}
|
|
166
|
+
if (!name) {
|
|
167
|
+
errors.push("You must specify the name of your package");
|
|
168
|
+
}
|
|
169
|
+
if (binaries && typeof binaries !== "object") {
|
|
170
|
+
errors.push("binaries must be a string => string map");
|
|
171
|
+
}
|
|
172
|
+
if (!binaries) {
|
|
173
|
+
errors.push("You must specify the binaries in the package");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (errors.length > 0) {
|
|
177
|
+
let errorMsg =
|
|
178
|
+
"One or more of the parameters you passed to the Binary constructor are invalid:\n";
|
|
179
|
+
errors.forEach((error) => {
|
|
180
|
+
errorMsg += error;
|
|
181
|
+
});
|
|
182
|
+
errorMsg +=
|
|
183
|
+
'\n\nCorrect usage: new Package("my-binary", "https://example.com/binary/download.tar.gz", {"my-binary": "my-binary"})';
|
|
184
|
+
error(errorMsg);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.platform = platform;
|
|
188
|
+
this.url = url;
|
|
189
|
+
this.name = name;
|
|
190
|
+
this.filename = filename;
|
|
191
|
+
this.zipExt = zipExt;
|
|
192
|
+
this.installDirectory = join(__dirname, "node_modules", ".bin_real");
|
|
193
|
+
this.binaries = binaries;
|
|
194
|
+
|
|
195
|
+
if (!existsSync(this.installDirectory)) {
|
|
196
|
+
mkdirSync(this.installDirectory, { recursive: true });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
exists() {
|
|
201
|
+
for (const binaryName in this.binaries) {
|
|
202
|
+
const binRelPath = this.binaries[binaryName];
|
|
203
|
+
const binPath = join(this.installDirectory, binRelPath);
|
|
204
|
+
if (!existsSync(binPath)) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
install(suppressLogs = false) {
|
|
212
|
+
if (this.exists()) {
|
|
213
|
+
if (!suppressLogs) {
|
|
214
|
+
console.error(
|
|
215
|
+
`${this.name} is already installed, skipping installation.`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return Promise.resolve();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
rmSync(this.installDirectory, { recursive: true, force: true });
|
|
223
|
+
} catch {
|
|
224
|
+
// ignore - directory may not exist
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
mkdirSync(this.installDirectory, { recursive: true });
|
|
228
|
+
|
|
229
|
+
if (!suppressLogs) {
|
|
230
|
+
console.error(`Downloading release from ${this.url}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return download(this.url)
|
|
234
|
+
.then((res) => {
|
|
235
|
+
return new Promise((resolve, reject) => {
|
|
236
|
+
mkdtemp(`${tmpDir}${sep}`, (err, directory) => {
|
|
237
|
+
if (err) return reject(err);
|
|
238
|
+
let tempFile = join(directory, this.filename);
|
|
239
|
+
const sink = res.pipe(createWriteStream(tempFile));
|
|
240
|
+
sink.on("error", (err) => reject(err));
|
|
241
|
+
sink.on("close", () => {
|
|
242
|
+
if (/\.tar\.*/.test(this.zipExt)) {
|
|
243
|
+
const result = spawnSync("tar", [
|
|
244
|
+
"xf",
|
|
245
|
+
tempFile,
|
|
246
|
+
// The tarballs are stored with a leading directory
|
|
247
|
+
// component; we strip one component in the
|
|
248
|
+
// shell installers too.
|
|
249
|
+
"--strip-components",
|
|
250
|
+
"1",
|
|
251
|
+
"-C",
|
|
252
|
+
this.installDirectory,
|
|
253
|
+
]);
|
|
254
|
+
if (result.status == 0) {
|
|
255
|
+
resolve();
|
|
256
|
+
} else if (result.error) {
|
|
257
|
+
reject(result.error);
|
|
258
|
+
} else {
|
|
259
|
+
reject(
|
|
260
|
+
new Error(
|
|
261
|
+
`An error occurred untarring the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
|
|
262
|
+
),
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
} else if (this.zipExt == ".zip") {
|
|
266
|
+
let result;
|
|
267
|
+
if (this.platform.artifactName.includes("windows")) {
|
|
268
|
+
// Windows does not have "unzip" by default on many installations, instead
|
|
269
|
+
// we use Expand-Archive from powershell
|
|
270
|
+
result = spawnSync("powershell.exe", [
|
|
271
|
+
"-NoProfile",
|
|
272
|
+
"-NonInteractive",
|
|
273
|
+
"-Command",
|
|
274
|
+
`& {
|
|
275
|
+
param([string]$LiteralPath, [string]$DestinationPath)
|
|
276
|
+
Expand-Archive -LiteralPath $LiteralPath -DestinationPath $DestinationPath -Force
|
|
277
|
+
}`,
|
|
278
|
+
tempFile,
|
|
279
|
+
this.installDirectory,
|
|
280
|
+
]);
|
|
281
|
+
} else {
|
|
282
|
+
result = spawnSync("unzip", [
|
|
283
|
+
"-q",
|
|
284
|
+
tempFile,
|
|
285
|
+
"-d",
|
|
286
|
+
this.installDirectory,
|
|
287
|
+
]);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (result.status == 0) {
|
|
291
|
+
resolve();
|
|
292
|
+
} else if (result.error) {
|
|
293
|
+
reject(result.error);
|
|
294
|
+
} else {
|
|
295
|
+
reject(
|
|
296
|
+
new Error(
|
|
297
|
+
`An error occurred unzipping the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
|
|
298
|
+
),
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
reject(
|
|
303
|
+
new Error(`Unrecognized file extension: ${this.zipExt}`),
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
})
|
|
310
|
+
.then(() => {
|
|
311
|
+
if (!suppressLogs) {
|
|
312
|
+
console.error(`${this.name} has been installed!`);
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
.catch((e) => {
|
|
316
|
+
error(`Error fetching release: ${e.message}`);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
run(binaryName) {
|
|
321
|
+
const promise = !this.exists() ? this.install(true) : Promise.resolve();
|
|
322
|
+
|
|
323
|
+
promise
|
|
324
|
+
.then(() => {
|
|
325
|
+
const [, , ...args] = process.argv;
|
|
326
|
+
|
|
327
|
+
const options = { cwd: process.cwd(), stdio: "inherit" };
|
|
328
|
+
|
|
329
|
+
const binRelPath = this.binaries[binaryName];
|
|
330
|
+
if (!binRelPath) {
|
|
331
|
+
error(`${binaryName} is not a known binary in ${this.name}`);
|
|
332
|
+
}
|
|
333
|
+
const binPath = join(this.installDirectory, binRelPath);
|
|
334
|
+
const result = spawnSync(binPath, args, options);
|
|
335
|
+
|
|
336
|
+
if (result.error) {
|
|
337
|
+
error(result.error);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
process.exit(result.status);
|
|
341
|
+
})
|
|
342
|
+
.catch((e) => {
|
|
343
|
+
error(e.message);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
module.exports.Package = Package;
|
package/binary.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const { Package } = require("./binary-install");
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const libc = require("detect-libc");
|
|
4
|
+
|
|
5
|
+
const error = (msg) => {
|
|
6
|
+
console.error(msg);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
name,
|
|
12
|
+
artifactDownloadUrls,
|
|
13
|
+
supportedPlatforms,
|
|
14
|
+
glibcMinimum,
|
|
15
|
+
} = require("./package.json");
|
|
16
|
+
|
|
17
|
+
// FIXME: implement NPM installer handling of fallback download URLs
|
|
18
|
+
const artifactDownloadUrl = artifactDownloadUrls[0];
|
|
19
|
+
const builderGlibcMajorVersion = glibcMinimum.major;
|
|
20
|
+
const builderGlibcMinorVersion = glibcMinimum.series;
|
|
21
|
+
|
|
22
|
+
const getPlatform = () => {
|
|
23
|
+
const rawOsType = os.type();
|
|
24
|
+
const rawArchitecture = os.arch();
|
|
25
|
+
|
|
26
|
+
// We want to use rust-style target triples as the canonical key
|
|
27
|
+
// for a platform, so translate the "os" library's concepts into rust ones
|
|
28
|
+
let osType = "";
|
|
29
|
+
switch (rawOsType) {
|
|
30
|
+
case "Windows_NT":
|
|
31
|
+
osType = "pc-windows-msvc";
|
|
32
|
+
break;
|
|
33
|
+
case "Darwin":
|
|
34
|
+
osType = "apple-darwin";
|
|
35
|
+
break;
|
|
36
|
+
case "Linux":
|
|
37
|
+
osType = "unknown-linux-gnu";
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let arch = "";
|
|
42
|
+
switch (rawArchitecture) {
|
|
43
|
+
case "x64":
|
|
44
|
+
arch = "x86_64";
|
|
45
|
+
break;
|
|
46
|
+
case "arm64":
|
|
47
|
+
arch = "aarch64";
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (rawOsType === "Linux") {
|
|
52
|
+
if (libc.familySync() == "musl") {
|
|
53
|
+
osType = "unknown-linux-musl-dynamic";
|
|
54
|
+
} else if (libc.isNonGlibcLinuxSync()) {
|
|
55
|
+
console.warn(
|
|
56
|
+
"Your libc is neither glibc nor musl; trying static musl binary instead",
|
|
57
|
+
);
|
|
58
|
+
osType = "unknown-linux-musl-static";
|
|
59
|
+
} else {
|
|
60
|
+
let libcVersion = libc.versionSync();
|
|
61
|
+
let splitLibcVersion = libcVersion.split(".");
|
|
62
|
+
let libcMajorVersion = splitLibcVersion[0];
|
|
63
|
+
let libcMinorVersion = splitLibcVersion[1];
|
|
64
|
+
if (
|
|
65
|
+
libcMajorVersion != builderGlibcMajorVersion ||
|
|
66
|
+
libcMinorVersion < builderGlibcMinorVersion
|
|
67
|
+
) {
|
|
68
|
+
// We can't run the glibc binaries, but we can run the static musl ones
|
|
69
|
+
// if they exist
|
|
70
|
+
console.warn(
|
|
71
|
+
"Your glibc isn't compatible; trying static musl binary instead",
|
|
72
|
+
);
|
|
73
|
+
osType = "unknown-linux-musl-static";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Assume the above succeeded and build a target triple to look things up with.
|
|
79
|
+
// If any of it failed, this lookup will fail and we'll handle it like normal.
|
|
80
|
+
let targetTriple = `${arch}-${osType}`;
|
|
81
|
+
let platform = supportedPlatforms[targetTriple];
|
|
82
|
+
|
|
83
|
+
if (!platform) {
|
|
84
|
+
error(
|
|
85
|
+
`Platform with type "${rawOsType}" and architecture "${rawArchitecture}" is not supported by ${name}.\nYour system must be one of the following:\n\n${Object.keys(
|
|
86
|
+
supportedPlatforms,
|
|
87
|
+
).join(",")}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return platform;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const getPackage = () => {
|
|
95
|
+
const platform = getPlatform();
|
|
96
|
+
const url = `${artifactDownloadUrl}/${platform.artifactName}`;
|
|
97
|
+
let filename = platform.artifactName;
|
|
98
|
+
let ext = platform.zipExt;
|
|
99
|
+
let binary = new Package(platform, name, url, filename, ext, platform.bins);
|
|
100
|
+
|
|
101
|
+
return binary;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const install = (suppressLogs) => {
|
|
105
|
+
if (!artifactDownloadUrl || artifactDownloadUrl.length === 0) {
|
|
106
|
+
console.warn("in demo mode, not installing binaries");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const pkg = getPackage();
|
|
110
|
+
|
|
111
|
+
return pkg.install(suppressLogs);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const run = (binaryName) => {
|
|
115
|
+
const pkg = getPackage();
|
|
116
|
+
|
|
117
|
+
pkg.run(binaryName);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
module.exports = {
|
|
121
|
+
install,
|
|
122
|
+
run,
|
|
123
|
+
getPackage,
|
|
124
|
+
};
|
package/install.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 3,
|
|
3
|
+
"name": "vallum",
|
|
4
|
+
"packages": {
|
|
5
|
+
"": {
|
|
6
|
+
"bin": {
|
|
7
|
+
"vallum": "run-vallum.js"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"detect-libc": "^2.1.2"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"prettier": "^3.8.3"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=14.14",
|
|
17
|
+
"npm": ">=6"
|
|
18
|
+
},
|
|
19
|
+
"hasInstallScript": true,
|
|
20
|
+
"license": "MIT OR Apache-2.0",
|
|
21
|
+
"name": "vallum",
|
|
22
|
+
"version": "0.3.0"
|
|
23
|
+
},
|
|
24
|
+
"node_modules/detect-libc": {
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=8"
|
|
27
|
+
},
|
|
28
|
+
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
|
29
|
+
"license": "Apache-2.0",
|
|
30
|
+
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
|
31
|
+
"version": "2.1.2"
|
|
32
|
+
},
|
|
33
|
+
"node_modules/prettier": {
|
|
34
|
+
"bin": {
|
|
35
|
+
"prettier": "bin/prettier.cjs"
|
|
36
|
+
},
|
|
37
|
+
"dev": true,
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=14"
|
|
40
|
+
},
|
|
41
|
+
"funding": {
|
|
42
|
+
"url": "https://github.com/prettier/prettier?sponsor=1"
|
|
43
|
+
},
|
|
44
|
+
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
|
|
47
|
+
"version": "3.8.3"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"requires": true,
|
|
51
|
+
"version": "0.3.0"
|
|
52
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"artifactDownloadUrls": [
|
|
3
|
+
"https://github.com/kahramanemir/Vallum/releases/download/v0.3.0"
|
|
4
|
+
],
|
|
5
|
+
"author": "Emir Kahraman",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vallum": "run-vallum.js"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"detect-libc": "^2.1.2"
|
|
11
|
+
},
|
|
12
|
+
"description": "A Rust CLI proxy between AI agents and your shell — sanitizes secrets, flags prompt injections, strips ANSI, compresses output, audits commands.",
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"prettier": "^3.8.3"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=14.14",
|
|
18
|
+
"npm": ">=6"
|
|
19
|
+
},
|
|
20
|
+
"glibcMinimum": {
|
|
21
|
+
"major": 2,
|
|
22
|
+
"series": 31
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/kahramanemir/Vallum",
|
|
25
|
+
"keywords": [
|
|
26
|
+
"command-line-utilities",
|
|
27
|
+
"ai",
|
|
28
|
+
"cli",
|
|
29
|
+
"proxy",
|
|
30
|
+
"security",
|
|
31
|
+
"tokens"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT OR Apache-2.0",
|
|
34
|
+
"name": "vallum",
|
|
35
|
+
"preferUnplugged": true,
|
|
36
|
+
"repository": "https://github.com/kahramanemir/Vallum",
|
|
37
|
+
"scripts": {
|
|
38
|
+
"fmt": "prettier --write **/*.js",
|
|
39
|
+
"fmt:check": "prettier --check **/*.js",
|
|
40
|
+
"postinstall": "node ./install.js"
|
|
41
|
+
},
|
|
42
|
+
"supportedPlatforms": {
|
|
43
|
+
"aarch64-apple-darwin": {
|
|
44
|
+
"artifactName": "vallum-aarch64-apple-darwin.tar.xz",
|
|
45
|
+
"bins": {
|
|
46
|
+
"vallum": "vallum"
|
|
47
|
+
},
|
|
48
|
+
"zipExt": ".tar.xz"
|
|
49
|
+
},
|
|
50
|
+
"aarch64-unknown-linux-gnu": {
|
|
51
|
+
"artifactName": "vallum-aarch64-unknown-linux-musl.tar.xz",
|
|
52
|
+
"bins": {
|
|
53
|
+
"vallum": "vallum"
|
|
54
|
+
},
|
|
55
|
+
"zipExt": ".tar.xz"
|
|
56
|
+
},
|
|
57
|
+
"aarch64-unknown-linux-musl-dynamic": {
|
|
58
|
+
"artifactName": "vallum-aarch64-unknown-linux-musl.tar.xz",
|
|
59
|
+
"bins": {
|
|
60
|
+
"vallum": "vallum"
|
|
61
|
+
},
|
|
62
|
+
"zipExt": ".tar.xz"
|
|
63
|
+
},
|
|
64
|
+
"aarch64-unknown-linux-musl-static": {
|
|
65
|
+
"artifactName": "vallum-aarch64-unknown-linux-musl.tar.xz",
|
|
66
|
+
"bins": {
|
|
67
|
+
"vallum": "vallum"
|
|
68
|
+
},
|
|
69
|
+
"zipExt": ".tar.xz"
|
|
70
|
+
},
|
|
71
|
+
"x86_64-apple-darwin": {
|
|
72
|
+
"artifactName": "vallum-x86_64-apple-darwin.tar.xz",
|
|
73
|
+
"bins": {
|
|
74
|
+
"vallum": "vallum"
|
|
75
|
+
},
|
|
76
|
+
"zipExt": ".tar.xz"
|
|
77
|
+
},
|
|
78
|
+
"x86_64-unknown-linux-gnu": {
|
|
79
|
+
"artifactName": "vallum-x86_64-unknown-linux-musl.tar.xz",
|
|
80
|
+
"bins": {
|
|
81
|
+
"vallum": "vallum"
|
|
82
|
+
},
|
|
83
|
+
"zipExt": ".tar.xz"
|
|
84
|
+
},
|
|
85
|
+
"x86_64-unknown-linux-musl-dynamic": {
|
|
86
|
+
"artifactName": "vallum-x86_64-unknown-linux-musl.tar.xz",
|
|
87
|
+
"bins": {
|
|
88
|
+
"vallum": "vallum"
|
|
89
|
+
},
|
|
90
|
+
"zipExt": ".tar.xz"
|
|
91
|
+
},
|
|
92
|
+
"x86_64-unknown-linux-musl-static": {
|
|
93
|
+
"artifactName": "vallum-x86_64-unknown-linux-musl.tar.xz",
|
|
94
|
+
"bins": {
|
|
95
|
+
"vallum": "vallum"
|
|
96
|
+
},
|
|
97
|
+
"zipExt": ".tar.xz"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"version": "0.3.0",
|
|
101
|
+
"volta": {
|
|
102
|
+
"node": "18.14.1",
|
|
103
|
+
"npm": "9.5.0"
|
|
104
|
+
}
|
|
105
|
+
}
|
package/run-vallum.js
ADDED