Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7455f76351 | ||
|
|
e1ea22c7f0 | ||
|
|
45b5cc15e0 | ||
|
|
9f36cc81d7 | ||
|
|
6fd7cf6618 | ||
|
|
4966d8f648 | ||
|
|
60d7cd677e | ||
|
|
03f44bc126 | ||
|
|
29bebd79ea |
102
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
102
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Something isn't working right
|
||||||
|
labels: ["bug"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for reporting! Please fill out the info below so we can debug faster.
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: Operating System
|
||||||
|
description: e.g. Ubuntu 24.04, macOS 15.2, Windows 11
|
||||||
|
placeholder: Ubuntu 24.04
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: mode
|
||||||
|
attributes:
|
||||||
|
label: Running Mode
|
||||||
|
description: How are you running ZeroGravity?
|
||||||
|
options:
|
||||||
|
- Headless (default, --headless)
|
||||||
|
- Classic (--classic, attached to running Antigravity)
|
||||||
|
- No MITM (--no-mitm)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: ZeroGravity Version / Commit
|
||||||
|
description: "Output of `git rev-parse --short HEAD` or the version you downloaded"
|
||||||
|
placeholder: 4966d8f
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: model
|
||||||
|
attributes:
|
||||||
|
label: Model Used
|
||||||
|
description: Which model were you using when the issue occurred?
|
||||||
|
placeholder: gemini-3-flash
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: Describe the issue clearly. What did you expect vs what actually happened?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Logs
|
||||||
|
description: |
|
||||||
|
Paste relevant log output. You can get logs with:
|
||||||
|
- `zg logs-all` (full logs)
|
||||||
|
- `journalctl --user -u zerogravity --no-pager -n 200` (systemd)
|
||||||
|
- Or check `~/.config/zerogravity/` for log files
|
||||||
|
render: text
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: trace
|
||||||
|
attributes:
|
||||||
|
label: Call Trace
|
||||||
|
description: |
|
||||||
|
If available, paste the trace summary for the failing request.
|
||||||
|
Traces are saved to `~/.config/zerogravity/traces/YYYY-MM-DD/`.
|
||||||
|
You can get the latest one with:
|
||||||
|
```
|
||||||
|
TRACE_DIR=~/.config/zerogravity/traces/$(date +%Y-%m-%d)
|
||||||
|
cat "$TRACE_DIR/$(ls -t "$TRACE_DIR" | head -1)/summary.md"
|
||||||
|
```
|
||||||
|
render: markdown
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: health
|
||||||
|
attributes:
|
||||||
|
label: Health Check Output
|
||||||
|
description: |
|
||||||
|
Paste the output of:
|
||||||
|
```
|
||||||
|
curl -s http://localhost:8741/health | jq .
|
||||||
|
zg status
|
||||||
|
```
|
||||||
|
render: json
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: repro
|
||||||
|
attributes:
|
||||||
|
label: Steps to Reproduce
|
||||||
|
description: How can we reproduce this? Include the curl command or client config if applicable.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: extra
|
||||||
|
attributes:
|
||||||
|
label: Additional Context
|
||||||
|
description: Anything else — screenshots, network setup (VPN/proxy), Antigravity version, etc.
|
||||||
23
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
23
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Suggest an idea or improvement
|
||||||
|
labels: ["enhancement"]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: What do you want?
|
||||||
|
description: Describe the feature or improvement you'd like to see.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: usecase
|
||||||
|
attributes:
|
||||||
|
label: Use Case
|
||||||
|
description: Why do you need this? What problem does it solve?
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Alternatives Considered
|
||||||
|
description: Any workarounds or alternative approaches you've tried?
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -2366,6 +2366,7 @@ dependencies = [
|
|||||||
"async-stream",
|
"async-stream",
|
||||||
"axum",
|
"axum",
|
||||||
"base64",
|
"base64",
|
||||||
|
"boring2",
|
||||||
"brotli 7.0.0",
|
"brotli 7.0.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -2386,6 +2387,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-boring2",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ rustls = { version = "0.23", features = ["ring"] }
|
|||||||
tokio-rustls = "0.26"
|
tokio-rustls = "0.26"
|
||||||
rustls-native-certs = "0.8"
|
rustls-native-certs = "0.8"
|
||||||
rustls-pemfile = "2"
|
rustls-pemfile = "2"
|
||||||
|
boring2 = "5.0.0-alpha.12"
|
||||||
|
tokio-boring2 = "5.0.0-alpha.12"
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
httparse = "1"
|
httparse = "1"
|
||||||
|
|||||||
@@ -20,8 +20,12 @@
|
|||||||
|
|
||||||
> **Early stage.** Ran this on OpenCode with an Ultra account for 3 days straight, stress testing the whole time. No issues so far. For comparison, a Pro account got banned after about 3 hours using CLIProxyAPI / Antigravity Manager. Not using a firewall since I'm living overseas, so your situation may vary if google detects any suspicious activity. PS: The tool call part is still unstable and may hang sometimes.
|
> **Early stage.** Ran this on OpenCode with an Ultra account for 3 days straight, stress testing the whole time. No issues so far. For comparison, a Pro account got banned after about 3 hours using CLIProxyAPI / Antigravity Manager. Not using a firewall since I'm living overseas, so your situation may vary if google detects any suspicious activity. PS: The tool call part is still unstable and may hang sometimes.
|
||||||
>
|
>
|
||||||
|
> This software is developed on Linux. I aim to support every OS as best as possible, so if there is any issue please open an issue and I will be happy to assist.
|
||||||
|
>
|
||||||
> Star the repo so more people can find it while it still works. Issues and PRs are welcome.
|
> Star the repo so more people can find it while it still works. Issues and PRs are welcome.
|
||||||
|
|
||||||
|
> **Should this stay open source?** [I need your help. Read the discussion and share your thoughts.](https://github.com/NikkeTryHard/zerogravity/discussions/10)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Skip Reading This — It's the Age of AI
|
## Skip Reading This — It's the Age of AI
|
||||||
@@ -57,7 +61,7 @@ Explain to the user what this project unlocks — not what it _is_, but what bec
|
|||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
The LS or Language Server is Antigravity's closed source Go binary that talks to Google's API over gRPC. The Extension Server is what feeds it auth tokens and settings/configs, we fake it with a stub so the LS thinks it's inside a real Antigravity window. ZeroGravity turns your OpenAI-compatible requests into dummy prompts and tells the LS to make an API call. The MITM proxy intercepts that call before it leaves the machine, swaps in your real prompt, tools, images, and generation params, re-encrypts it with BoringSSL matching Chrome's exact TLS fingerprint, and forwards it to Google. Google sees what looks like a normal Antigravity session. The response streams back as SSE events which the MITM parses for text, thinking tokens, tool calls, and usage. The iptables redirect is a UID-scoped firewall rule that routes only the LS's traffic through the MITM without touching anything else.
|
The LS or Language Server is Antigravity's closed source Go binary that talks to Google's API over gRPC. The Extension Server is what feeds it auth tokens and settings/configs, we fake it with a stub so the LS thinks it's inside a real Antigravity window. ZeroGravity turns your OpenAI-compatible requests into dummy prompts and tells the LS to make an API call. The MITM proxy intercepts that call before it leaves the machine, swaps in your real prompt, tools, images, and generation params, re-encrypts it over TLS, and forwards it to Google. All proxy-to-LS communication uses BoringSSL with Chrome's exact TLS and HTTP/2 fingerprint so the LS can't tell it's not a real Antigravity window. Google sees what looks like a normal Antigravity session. The response streams back as SSE events which the MITM parses for text, thinking tokens, tool calls, and usage. The iptables redirect is a UID-scoped firewall rule that routes only the LS's traffic through the MITM without touching anything else.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
%%{init: {'theme': 'dark', 'themeVariables': {'primaryColor': '#2a2a2a', 'primaryTextColor': '#d0d0d0', 'primaryBorderColor': '#888', 'lineColor': '#888', 'secondaryColor': '#333', 'tertiaryColor': '#3a3a3a', 'edgeLabelBackground': '#2a2a2a', 'nodeTextColor': '#d0d0d0'}}}%%
|
%%{init: {'theme': 'dark', 'themeVariables': {'primaryColor': '#2a2a2a', 'primaryTextColor': '#d0d0d0', 'primaryBorderColor': '#888', 'lineColor': '#888', 'secondaryColor': '#333', 'tertiaryColor': '#3a3a3a', 'edgeLabelBackground': '#2a2a2a', 'nodeTextColor': '#d0d0d0'}}}%%
|
||||||
|
|||||||
@@ -505,6 +505,76 @@ Team/organization management. Handles licensing, seats, credits.
|
|||||||
- `UpdateCascadeWebSearchEnabled` — toggle web search
|
- `UpdateCascadeWebSearchEnabled` — toggle web search
|
||||||
- `SetUserApiProviderKey` — BYOK key management
|
- `SetUserApiProviderKey` — BYOK key management
|
||||||
|
|
||||||
|
### AnalyticsService (7+ methods)
|
||||||
|
|
||||||
|
Separate analytics service for recording telemetry and training data.
|
||||||
|
|
||||||
|
```
|
||||||
|
BatchRecordCompletions
|
||||||
|
BatchRecordPrompts
|
||||||
|
RecordCommandUsage
|
||||||
|
RecordCompletions
|
||||||
|
RecordContextToPrompt
|
||||||
|
RecordCortexTrajectory
|
||||||
|
RecordCortexTrajectoryStep
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notable:** Distinct from `ApiServerService.Record*` — likely a dedicated analytics pipeline.
|
||||||
|
|
||||||
|
### UserAnalyticsService (5+ methods)
|
||||||
|
|
||||||
|
Per-user analytics dashboard service.
|
||||||
|
|
||||||
|
```
|
||||||
|
Analytics
|
||||||
|
CascadeAnalytics
|
||||||
|
GetAnalytics
|
||||||
|
GetPreferredTimeZone
|
||||||
|
UserPageAnalytics
|
||||||
|
```
|
||||||
|
|
||||||
|
### KnowledgeBaseService (17+ methods)
|
||||||
|
|
||||||
|
Enterprise knowledge base with multi-source ingestion. Uses OpenSearch backend.
|
||||||
|
|
||||||
|
```
|
||||||
|
AddGithubUsers
|
||||||
|
AddUsers
|
||||||
|
CancelKnowledgeBaseJobs
|
||||||
|
ConnectKnowledgeBaseAccount
|
||||||
|
DeleteKnowledgeBaseConnection
|
||||||
|
ForwardSlackPayload
|
||||||
|
GetConnectorInternalConfig
|
||||||
|
GetKnowledgeBaseConnectorState
|
||||||
|
GetKnowledgeBaseItemsFromScopeItems
|
||||||
|
GetKnowledgeBaseJobStates
|
||||||
|
GetKnowledgeBaseScopeItems
|
||||||
|
GetKnowledgeBaseWebhookUrl
|
||||||
|
IngestGithubData
|
||||||
|
IngestGoogleDriveData
|
||||||
|
IngestJiraData
|
||||||
|
IngestJiraPayload
|
||||||
|
IngestSlackData
|
||||||
|
IngestSlackPayload
|
||||||
|
KnowledgeBaseSearch
|
||||||
|
UpdateConnectorConfig
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key insight:** Supports ingesting from **GitHub**, **Google Drive**, **Jira**, and **Slack** — full enterprise knowledge integration.
|
||||||
|
|
||||||
|
### CodeIndexService (4+ methods)
|
||||||
|
|
||||||
|
Code search and indexing. Uses OpenSearch for hybrid vector+keyword search.
|
||||||
|
|
||||||
|
```
|
||||||
|
GraphSearch
|
||||||
|
HybridSearch
|
||||||
|
OpenSearchAddRepository
|
||||||
|
OpenSearchGetIndex
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notable:** `GraphSearch` suggests code graph traversal, `HybridSearch` combines vector embeddings with keyword matching.
|
||||||
|
|
||||||
### ModelManagementService (8 methods)
|
### ModelManagementService (8 methods)
|
||||||
|
|
||||||
Self-hosted model management for hybrid deployments.
|
Self-hosted model management for hybrid deployments.
|
||||||
@@ -935,6 +1005,539 @@ https://iamcredentials.googleapis.com/v1/ ← Service account impersonation
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Memory & Brain System
|
||||||
|
|
||||||
|
The LS has a sophisticated memory system that persists across conversations.
|
||||||
|
|
||||||
|
### Memory Sources
|
||||||
|
|
||||||
|
```
|
||||||
|
MEMORY_SOURCE_UNSPECIFIED
|
||||||
|
MEMORY_SOURCE_USER — manually created by user
|
||||||
|
MEMORY_SOURCE_CASCADE — extracted from cascade conversations
|
||||||
|
MEMORY_SOURCE_AUTO_CASCADE — automatically generated from cascades
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory Triggers
|
||||||
|
|
||||||
|
```
|
||||||
|
MEMORY_TRIGGER_UNSPECIFIED
|
||||||
|
MEMORY_TRIGGER_ALWAYS_ON — always active
|
||||||
|
MEMORY_TRIGGER_GLOB — glob pattern match on file paths
|
||||||
|
MEMORY_TRIGGER_MANUAL — only when explicitly invoked
|
||||||
|
MEMORY_TRIGGER_MODEL_DECISION — model decides when to use
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory Actions
|
||||||
|
|
||||||
|
```
|
||||||
|
MEMORY_ACTION_TYPE_UNSPECIFIED
|
||||||
|
MEMORY_ACTION_TYPE_CREATE
|
||||||
|
MEMORY_ACTION_TYPE_UPDATE
|
||||||
|
MEMORY_ACTION_TYPE_DELETE
|
||||||
|
```
|
||||||
|
|
||||||
|
### Brain Entry Types
|
||||||
|
|
||||||
|
```
|
||||||
|
BRAIN_ENTRY_TYPE_UNSPECIFIED
|
||||||
|
BRAIN_ENTRY_TYPE_PLAN — implementation plans
|
||||||
|
BRAIN_ENTRY_TYPE_TASK — task checklists
|
||||||
|
```
|
||||||
|
|
||||||
|
### Brain Update Triggers
|
||||||
|
|
||||||
|
```
|
||||||
|
BRAIN_UPDATE_TRIGGER_UNSPECIFIED
|
||||||
|
BRAIN_UPDATE_TRIGGER_USER_REQUESTED — user explicitly asks
|
||||||
|
BRAIN_UPDATE_TRIGGER_USER_NEW_INFO — user provides new info
|
||||||
|
BRAIN_UPDATE_TRIGGER_RESEARCH_NEW_INFO — research discovers new info
|
||||||
|
BRAIN_UPDATE_TRIGGER_SYSTEM_FORCED — system-initiated update
|
||||||
|
```
|
||||||
|
|
||||||
|
### Brain Filter Strategies
|
||||||
|
|
||||||
|
```
|
||||||
|
BRAIN_FILTER_STRATEGY_UNSPECIFIED
|
||||||
|
BRAIN_FILTER_STRATEGY_NO_MEMORIES — exclude all memories
|
||||||
|
BRAIN_FILTER_STRATEGY_NO_SYSTEM_INJECTED_STEPS — exclude system steps
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Expanded Cascade Configuration Keys
|
||||||
|
|
||||||
|
Full list of all cascade config keys extracted from binary (60+ keys):
|
||||||
|
|
||||||
|
### Core Behavior
|
||||||
|
|
||||||
|
```
|
||||||
|
CASCADE_ENFORCE_QUOTA — quota enforcement toggle
|
||||||
|
CASCADE_BASE_MODEL_ID — default model selection
|
||||||
|
CASCADE_DEFAULT_MODEL_OVERRIDE — model override
|
||||||
|
CASCADE_GLOBAL_CONFIG_OVERRIDE — global cascade config
|
||||||
|
CASCADE_MEMORY_CONFIG_OVERRIDE — memory subsystem config
|
||||||
|
CASCADE_PLAN_BASED_CONFIG_OVERRIDE — planner config
|
||||||
|
CASCADE_FREE_CONFIG_OVERRIDE — free-tier config
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tool Access
|
||||||
|
|
||||||
|
```
|
||||||
|
CASCADE_ENABLE_MCP_TOOLS — MCP tool use
|
||||||
|
CASCADE_ANTIGRAVITY_BROWSER_TOOLS_ENABLED
|
||||||
|
CASCADE_ENABLE_PROXY_WEB_SERVER — proxy web server
|
||||||
|
CASCADE_ENABLE_IDE_TERMINAL_EXECUTION
|
||||||
|
CASCADE_VIEW_FILE_TOOL_CONFIG_OVERRIDE
|
||||||
|
CASCADE_USE_REPLACE_CONTENT_EDIT_TOOL
|
||||||
|
```
|
||||||
|
|
||||||
|
### Web Search
|
||||||
|
|
||||||
|
```
|
||||||
|
CASCADE_WEB_SEARCH_TOOL_ENABLED
|
||||||
|
CASCADE_WEB_SEARCH_TOOL_DISABLED
|
||||||
|
CASCADE_WEB_SEARCH_TOOL_UNSPECIFIED
|
||||||
|
CASCADE_WEB_APP_DEPLOYMENTS_ENABLED
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extension Code Execution
|
||||||
|
|
||||||
|
```
|
||||||
|
CASCADE_RUN_EXTENSION_CODE_ENABLED
|
||||||
|
CASCADE_RUN_EXTENSION_CODE_DISABLED
|
||||||
|
CASCADE_RUN_EXTENSION_CODE_ONLY
|
||||||
|
CASCADE_RUN_EXTENSION_CODE_AUTO_RUN_ENABLED
|
||||||
|
CASCADE_RUN_EXTENSION_CODE_AUTO_RUN_MODEL_DECIDES
|
||||||
|
CASCADE_RUN_EXTENSION_CODE_AUTO_RUN_UNSPECIFIED
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command Auto-Execution Policy
|
||||||
|
|
||||||
|
```
|
||||||
|
CASCADE_COMMANDS_AUTO_EXECUTION_UNSPECIFIED
|
||||||
|
CASCADE_COMMANDS_AUTO_EXECUTION_OFF — always ask
|
||||||
|
CASCADE_COMMANDS_AUTO_EXECUTION_AUTO — auto-run safe commands
|
||||||
|
CASCADE_COMMANDS_AUTO_EXECUTION_EAGER — auto-run most commands
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory & Learning
|
||||||
|
|
||||||
|
```
|
||||||
|
CASCADE_ENABLE_AUTOMATED_MEMORIES — auto-generate memories
|
||||||
|
CASCADE_USER_MEMORIES_IN_SYS_PROMPT
|
||||||
|
CASCADE_ENABLE_CUSTOM_RECIPES
|
||||||
|
CASCADE_BACKGROUND_RESEARCH_CONFIG_OVERRIDE
|
||||||
|
CASCADE_USE_EXPERIMENT_CHECKPOINTER
|
||||||
|
CASCADE_USE_SUBAGENT_CHECKPOINTER
|
||||||
|
```
|
||||||
|
|
||||||
|
### Autocomplete & Input
|
||||||
|
|
||||||
|
```
|
||||||
|
CASCADE_INPUT_AUTOCOMPLETE_ENABLED
|
||||||
|
CASCADE_INPUT_AUTOCOMPLETE_DISABLED
|
||||||
|
CASCADE_INPUT_AUTOCOMPLETE_UNSPECIFIED
|
||||||
|
CASCADE_RECIPES_AT_MENTION_VISIBILITY
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Status
|
||||||
|
|
||||||
|
```
|
||||||
|
CASCADE_RUN_STATUS_UNSPECIFIED
|
||||||
|
CASCADE_RUN_STATUS_IDLE
|
||||||
|
CASCADE_RUN_STATUS_BUSY
|
||||||
|
CASCADE_RUN_STATUS_RUNNING
|
||||||
|
CASCADE_RUN_STATUS_CANCELING
|
||||||
|
```
|
||||||
|
|
||||||
|
### Edit Importance (for code diffs)
|
||||||
|
|
||||||
|
```
|
||||||
|
CASCADE_EDIT_IMPORTANCE_UNSPECIFIED
|
||||||
|
CASCADE_EDIT_IMPORTANCE_LOW
|
||||||
|
CASCADE_EDIT_IMPORTANCE_MEDIUM
|
||||||
|
CASCADE_EDIT_IMPORTANCE_HIGH
|
||||||
|
```
|
||||||
|
|
||||||
|
### Seat Types
|
||||||
|
|
||||||
|
```
|
||||||
|
CASCADE_SEAT_TYPE_UNSPECIFIED
|
||||||
|
CASCADE_SEAT_TYPE_ENTRY — entry tier
|
||||||
|
CASCADE_SEAT_TYPE_STANDARD — standard tier
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Supercomplete (Tab Completion) System
|
||||||
|
|
||||||
|
The "Supercomplete" system powers multi-line tab completions, distinct from basic autocomplete.
|
||||||
|
|
||||||
|
### Trigger Conditions
|
||||||
|
|
||||||
|
```
|
||||||
|
SUPERCOMPLETE_TRIGGER_CONDITION_UNSPECIFIED
|
||||||
|
SUPERCOMPLETE_TRIGGER_CONDITION_TYPING
|
||||||
|
SUPERCOMPLETE_TRIGGER_CONDITION_AUTOCOMPLETE_ACCEPT
|
||||||
|
SUPERCOMPLETE_TRIGGER_CONDITION_AUTOCOMPLETE_PREDICTIVE
|
||||||
|
SUPERCOMPLETE_TRIGGER_CONDITION_SUPERCOMPLETE_ACCEPT
|
||||||
|
SUPERCOMPLETE_TRIGGER_CONDITION_SUPERCOMPLETE_PREDICTIVE
|
||||||
|
SUPERCOMPLETE_TRIGGER_CONDITION_TAB_JUMP_ACCEPT
|
||||||
|
SUPERCOMPLETE_TRIGGER_CONDITION_TAB_JUMP_EDIT
|
||||||
|
SUPERCOMPLETE_TRIGGER_CONDITION_TAB_JUMP_PREDICTIVE
|
||||||
|
SUPERCOMPLETE_TRIGGER_CONDITION_CURSOR_LINE_NAVIGATION
|
||||||
|
SUPERCOMPLETE_TRIGGER_CONDITION_FORCED
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filters
|
||||||
|
|
||||||
|
```
|
||||||
|
SUPERCOMPLETE_FILTER_PREFIX_MATCH
|
||||||
|
SUPERCOMPLETE_FILTER_SUFFIX_MATCH
|
||||||
|
SUPERCOMPLETE_FILTER_INSERTION_CAP
|
||||||
|
SUPERCOMPLETE_FILTER_DELETION_CAP
|
||||||
|
SUPERCOMPLETE_FILTER_NO_OP
|
||||||
|
SUPERCOMPLETE_FILTER_REVERT
|
||||||
|
SUPERCOMPLETE_FILTER_PREVIOUSLY_SHOWN
|
||||||
|
SUPERCOMPLETE_FILTER_WHITESPACE_ONLY
|
||||||
|
SUPERCOMPLETE_FILTER_SCORE_THRESHOLD
|
||||||
|
```
|
||||||
|
|
||||||
|
### Config Keys
|
||||||
|
|
||||||
|
```
|
||||||
|
SUPERCOMPLETE_TEMPERATURE
|
||||||
|
SUPERCOMPLETE_MODEL_CONFIG
|
||||||
|
SUPERCOMPLETE_LINE_RADIUS
|
||||||
|
SUPERCOMPLETE_MIN_SCORE
|
||||||
|
SUPERCOMPLETE_MAX_INSERTIONS
|
||||||
|
SUPERCOMPLETE_MAX_DELETIONS
|
||||||
|
SUPERCOMPLETE_MAX_TRAJECTORY_STEPS
|
||||||
|
SUPERCOMPLETE_MAX_TRAJECTORY_STEP_SIZE
|
||||||
|
SUPERCOMPLETE_MAX_CONCURRENT_REQUESTS
|
||||||
|
SUPERCOMPLETE_FAST_DEBOUNCE
|
||||||
|
SUPERCOMPLETE_REGULAR_DEBOUNCE
|
||||||
|
SUPERCOMPLETE_RECENT_STEPS_DURATION
|
||||||
|
SUPERCOMPLETE_USE_CODE_DIAGNOSTICS
|
||||||
|
SUPERCOMPLETE_CODE_DIAGNOSTICS_TOP_K
|
||||||
|
SUPERCOMPLETE_DIAGNOSTIC_SEVERITY_THRESHOLD
|
||||||
|
SUPERCOMPLETE_DISABLE_TYPING_CACHE
|
||||||
|
SUPERCOMPLETE_DONT_FILTER_MID_STREAMED
|
||||||
|
SUPERCOMPLETE_ALWAYS_USE_CACHE_ON_EQUAL_STATE
|
||||||
|
SUPERCOMPLETE_CACHE_ON_PARENT_ID_KILL_SWITCH
|
||||||
|
SUPERCOMPLETE_ON_ACCEPT_ONLY
|
||||||
|
SUPERCOMPLETE_PRUNE_RESPONSE
|
||||||
|
SUPERCOMPLETE_PRUNE_MAX_INSERT_DELETE_LINE_DELTA
|
||||||
|
SUPERCOMPLETE_INLINE_PURE_DELETE
|
||||||
|
SUPERCOMPLETE_INLINE_RICH_GHOST_TEXT_INSERTIONS
|
||||||
|
SUPERCOMPLETE_USE_CURRENT_LINE
|
||||||
|
SUPERCOMPLETE_NO_ACTIVE_NODE
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tab Jump (Cursor Navigation)
|
||||||
|
|
||||||
|
```
|
||||||
|
TAB_JUMP_ENABLED
|
||||||
|
TAB_JUMP_LINE_RADIUS
|
||||||
|
TAB_JUMP_CUMULATIVE_PROMPT_CONFIG
|
||||||
|
TAB_JUMP_FILTER_IN_SELECTION
|
||||||
|
TAB_JUMP_FILTER_INSERTION_CAP
|
||||||
|
TAB_JUMP_FILTER_DELETION_CAP
|
||||||
|
TAB_JUMP_FILTER_SCORE_THRESHOLD
|
||||||
|
TAB_JUMP_FILTER_WHITESPACE_ONLY
|
||||||
|
TAB_JUMP_FILTER_NO_OP
|
||||||
|
TAB_JUMP_FILTER_REVERT
|
||||||
|
TAB_JUMP_STOP_TOKEN_MIDSTREAM
|
||||||
|
TAB_JUMP_ON_ACCEPT_ONLY
|
||||||
|
TAB_JUMP_PRUNE_RESPONSE
|
||||||
|
TAB_JUMP_PRUNE_MAX_INSERT_DELETE_LINE_DELTA
|
||||||
|
TAB_JUMP_MIN_FILTER_RADIUS
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Browser Automation Policies
|
||||||
|
|
||||||
|
### JS Execution Policy (user can control)
|
||||||
|
|
||||||
|
```
|
||||||
|
BROWSER_JS_EXECUTION_POLICY_UNSPECIFIED
|
||||||
|
BROWSER_JS_EXECUTION_POLICY_ALWAYS_ASK
|
||||||
|
BROWSER_JS_EXECUTION_POLICY_MODEL_DECIDES
|
||||||
|
BROWSER_JS_EXECUTION_POLICY_TURBO — never ask
|
||||||
|
BROWSER_JS_EXECUTION_POLICY_DISABLED
|
||||||
|
```
|
||||||
|
|
||||||
|
### JS Auto-Run Policy (cascade-level)
|
||||||
|
|
||||||
|
```
|
||||||
|
BROWSER_JS_AUTO_RUN_POLICY_UNSPECIFIED
|
||||||
|
BROWSER_JS_AUTO_RUN_POLICY_ENABLED
|
||||||
|
BROWSER_JS_AUTO_RUN_POLICY_DISABLED
|
||||||
|
BROWSER_JS_AUTO_RUN_POLICY_MODEL_DECIDES
|
||||||
|
```
|
||||||
|
|
||||||
|
### Browser Subagent Mode
|
||||||
|
|
||||||
|
```
|
||||||
|
BROWSER_SUBAGENT_MODE_UNSPECIFIED
|
||||||
|
BROWSER_SUBAGENT_MODE_MAIN_AGENT_ONLY
|
||||||
|
BROWSER_SUBAGENT_MODE_SUBAGENT_ONLY
|
||||||
|
BROWSER_SUBAGENT_MODE_SUBAGENT_PRIMARILY
|
||||||
|
BROWSER_SUBAGENT_MODE_BOTH_AGENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tool Set Mode (input/output level)
|
||||||
|
|
||||||
|
```
|
||||||
|
BROWSER_TOOL_SET_MODE_UNSPECIFIED
|
||||||
|
BROWSER_TOOL_SET_MODE_ALL_TOOLS
|
||||||
|
BROWSER_TOOL_SET_MODE_PIXEL_ONLY
|
||||||
|
BROWSER_TOOL_SET_MODE_ALL_INPUT_PIXEL_OUTPUT
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ephemeral Options (what gets captured)
|
||||||
|
|
||||||
|
```
|
||||||
|
BROWSER_EPHEMERAL_OPTION_UNSPECIFIED
|
||||||
|
BROWSER_EPHEMERAL_OPTION_DOM
|
||||||
|
BROWSER_EPHEMERAL_OPTION_SCREENSHOT
|
||||||
|
```
|
||||||
|
|
||||||
|
### Action Waiting Reasons
|
||||||
|
|
||||||
|
```
|
||||||
|
BROWSER_ACTION_WAITING_REASON_UNSPECIFIED
|
||||||
|
BROWSER_ACTION_WAITING_REASON_PAGE_ACCESS
|
||||||
|
BROWSER_ACTION_WAITING_REASON_ACTION_PERMISSION
|
||||||
|
BROWSER_ACTION_WAITING_REASON_PAGE_ACCESS_AND_ACTION_PERMISSION
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installation Status
|
||||||
|
|
||||||
|
```
|
||||||
|
BROWSER_INSTALLATION_STATUS_UNSPECIFIED
|
||||||
|
BROWSER_INSTALLATION_STATUS_NOT_INSTALLED
|
||||||
|
BROWSER_INSTALLATION_STATUS_IN_PROGRESS
|
||||||
|
BROWSER_INSTALLATION_STATUS_COMPLETE
|
||||||
|
BROWSER_INSTALLATION_STATUS_ERROR
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sandbox System (macOS Seatbelt)
|
||||||
|
|
||||||
|
The LS includes a `sandbox-wrapper.sh` that sandboxes command execution on macOS using Apple's Seatbelt framework.
|
||||||
|
|
||||||
|
### Key Behaviors
|
||||||
|
|
||||||
|
- **File writes**: Denied by default, allowed only to workspace dir and `/tmp`
|
||||||
|
- **Network**: Denied unless `--allow-network` flag is passed
|
||||||
|
- **Gitignore**: Reads `.gitignore` and `.agyignore` to deny access to ignored files
|
||||||
|
- **Process isolation**: Uses `sandbox-exec` with a generated Seatbelt profile
|
||||||
|
- **Violation detection**: Detects "Operation not permitted" errors and suggests disabling sandbox
|
||||||
|
|
||||||
|
### `.agyignore` File
|
||||||
|
|
||||||
|
Custom ignore file (like `.gitignore`) specifically for the sandbox. Patterns in `.agyignore` deny both read and write access within the sandbox.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Trainer / RLHF Infrastructure
|
||||||
|
|
||||||
|
The binary contains extensive proto definitions for model training, suggesting the LS participates in or configures training runs.
|
||||||
|
|
||||||
|
### Proto Package: `exa.trainer_pb`
|
||||||
|
|
||||||
|
**Training Objectives:**
|
||||||
|
|
||||||
|
```
|
||||||
|
DPOConfig — Direct Preference Optimization
|
||||||
|
KTOConfig — Kahneman-Tversky Optimization
|
||||||
|
OnPolicyGRPOConfig — Group Relative Policy Optimization
|
||||||
|
KnowledgeDistillationConfig
|
||||||
|
PreferenceLoss — preference-based loss functions
|
||||||
|
```
|
||||||
|
|
||||||
|
**Model Architecture:**
|
||||||
|
|
||||||
|
```
|
||||||
|
BaseModelConfig
|
||||||
|
EncoderDecoderModelConfig
|
||||||
|
MixtureOfExpertsConfig — MoE architecture
|
||||||
|
MultiLatentAttentionConfig — MLA (DeepSeek-style?)
|
||||||
|
MultiTokenPredictionConfig
|
||||||
|
CrossAttentionConfig
|
||||||
|
SlidingWindowAttentionConfig
|
||||||
|
```
|
||||||
|
|
||||||
|
**Quantization:**
|
||||||
|
|
||||||
|
```
|
||||||
|
QuantizationConfig
|
||||||
|
QuantizationMethod
|
||||||
|
QuantizationPrecision
|
||||||
|
QuantizationPolicy / PolicySet
|
||||||
|
LinearQuantization
|
||||||
|
LayerNormQuantization
|
||||||
|
```
|
||||||
|
|
||||||
|
**Training Infrastructure:**
|
||||||
|
|
||||||
|
```
|
||||||
|
OptimizerType / OptimizerExpAvgDtype
|
||||||
|
CheckpointBackend (TORCH_DCP, ZARR)
|
||||||
|
ParallelismConfig / LayerParallelismConfig
|
||||||
|
RoPEEmbeddingConfig (Llama, DeepseekV scaling)
|
||||||
|
SGLangInferenceProviderConfig
|
||||||
|
MockedInferenceProviderConfig
|
||||||
|
```
|
||||||
|
|
||||||
|
**Experiment Management:**
|
||||||
|
|
||||||
|
```
|
||||||
|
ExperimentProject / ExperimentMetadata
|
||||||
|
SweepAxis / SweepItem
|
||||||
|
ScheduledModel / SchedulingGroupConfig
|
||||||
|
DeviceWorkerRoutingInfo
|
||||||
|
DraftTargetModelPair — speculative decoding?
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Proto Packages
|
||||||
|
|
||||||
|
```
|
||||||
|
exa.cortex_pb — agent/cascade core
|
||||||
|
exa.chat_pb — chat message/conversation protos
|
||||||
|
exa.trainer_pb — training/RLHF
|
||||||
|
exa.analytics_pb — telemetry
|
||||||
|
exa.api_server_pb — cloud API server
|
||||||
|
exa.seat_management_pb — user/team management
|
||||||
|
exa.user_analytics_pb — user analytics
|
||||||
|
exa.opensearch_clients_pb — knowledge base / code index
|
||||||
|
exa.language_server_pb — LS service
|
||||||
|
exa.extension_server_pb — extension server
|
||||||
|
exa.model_management_pb — model management
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MCP Integration Constants
|
||||||
|
|
||||||
|
```
|
||||||
|
MCP_SERVER_STATUS_UNSPECIFIED
|
||||||
|
MCP_SERVER_STATUS_PENDING
|
||||||
|
MCP_SERVER_STATUS_READY
|
||||||
|
MCP_SERVER_STATUS_ERROR
|
||||||
|
MCP_ADD_SERVER
|
||||||
|
MCP_SAVE_CONFIG
|
||||||
|
MCP_EXPAND_TOOLS
|
||||||
|
MCP_TOOL
|
||||||
|
MCP_RESOURCE
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cortex Step Statuses
|
||||||
|
|
||||||
|
```
|
||||||
|
CORTEX_STEP_STATUS_UNSPECIFIED
|
||||||
|
CORTEX_STEP_STATUS_PENDING
|
||||||
|
CORTEX_STEP_STATUS_QUEUED
|
||||||
|
CORTEX_STEP_STATUS_GENERATING
|
||||||
|
CORTEX_STEP_STATUS_RUNNING
|
||||||
|
CORTEX_STEP_STATUS_WAITING
|
||||||
|
CORTEX_STEP_STATUS_DONE
|
||||||
|
CORTEX_STEP_STATUS_ERROR
|
||||||
|
CORTEX_STEP_STATUS_CANCELED
|
||||||
|
CORTEX_STEP_STATUS_CLEARED
|
||||||
|
CORTEX_STEP_STATUS_HALTED
|
||||||
|
CORTEX_STEP_STATUS_INTERRUPTED
|
||||||
|
CORTEX_STEP_STATUS_INVALID
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step Sources
|
||||||
|
|
||||||
|
```
|
||||||
|
CORTEX_STEP_SOURCE_UNSPECIFIED
|
||||||
|
CORTEX_STEP_SOURCE_MODEL — generated by LLM
|
||||||
|
CORTEX_STEP_SOURCE_SYSTEM — system-generated
|
||||||
|
CORTEX_STEP_SOURCE_SYSTEM_SDK — SDK-generated
|
||||||
|
CORTEX_STEP_SOURCE_USER_EXPLICIT — user-initiated
|
||||||
|
CORTEX_STEP_SOURCE_USER_IMPLICIT — implied by user action
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step Credit Reasons
|
||||||
|
|
||||||
|
```
|
||||||
|
CORTEX_STEP_CREDIT_REASON_UNSPECIFIED
|
||||||
|
CORTEX_STEP_CREDIT_REASON_LINT_FIXING_DISCOUNT — lint fixes are discounted
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manager Feedback Status
|
||||||
|
|
||||||
|
```
|
||||||
|
CORTEX_STEP_MANAGER_FEEDBACK_STATUS_UNSPECIFIED
|
||||||
|
CORTEX_STEP_MANAGER_FEEDBACK_STATUS_APPROVED
|
||||||
|
CORTEX_STEP_MANAGER_FEEDBACK_STATUS_DENIED
|
||||||
|
CORTEX_STEP_MANAGER_FEEDBACK_STATUS_ERROR
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Categories
|
||||||
|
|
||||||
|
```
|
||||||
|
CORTEX_ERROR_CATEGORY_UNSPECIFIED
|
||||||
|
CORTEX_ERROR_CATEGORY_OVERALL
|
||||||
|
CORTEX_ERROR_CATEGORY_ACTION_PREPARE
|
||||||
|
CORTEX_ERROR_CATEGORY_ACTION_APPLY
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile Tools (Lint Integration)
|
||||||
|
|
||||||
|
```
|
||||||
|
CORTEX_STEP_COMPILE_TOOL_UNSPECIFIED
|
||||||
|
CORTEX_STEP_COMPILE_TOOL_PYLINT
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Onboarding Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
ONBOARDING_PROGRESS_UNKNOWN
|
||||||
|
ONBOARDING_PROGRESS_NONE
|
||||||
|
ONBOARDING_PROGRESS_ELIGIBLE
|
||||||
|
ONBOARDING_PROGRESS_ONBOARDED
|
||||||
|
|
||||||
|
ONBOARDING_ACTION_TYPE_UNSPECIFIED
|
||||||
|
ONBOARDING_ACTION_TYPE_CHAT
|
||||||
|
ONBOARDING_ACTION_TYPE_AUTOCOMPLETE
|
||||||
|
ONBOARDING_ACTION_TYPE_COMMAND
|
||||||
|
|
||||||
|
ONBOARDING_PAGE_TYPE_UNSPECIFIED
|
||||||
|
ONBOARDING_PAGE_TYPE_HOTKEY
|
||||||
|
ONBOARDING_PAGE_TYPE_LENS
|
||||||
|
ONBOARDING_PAGE_TYPE_SEARCH
|
||||||
|
```
|
||||||
|
|
||||||
|
**Onboarding pages discovered:**
|
||||||
|
|
||||||
|
```
|
||||||
|
ONBOARDING_LANDING_PAGE_OPENED
|
||||||
|
ONBOARDING_AUTH_PAGE_OPENED
|
||||||
|
ONBOARDING_AUTH_MANUAL_PAGE_OPENED
|
||||||
|
ONBOARDING_CHOOSE_THEME_PAGE_OPENED
|
||||||
|
ONBOARDING_KEYBINDINGS_PAGE_OPENED
|
||||||
|
ONBOARDING_AGENT_CONFIG_PAGE_OPENED
|
||||||
|
ONBOARDING_IMPORT_PAGE_OPENED
|
||||||
|
ONBOARDING_MIGRATION_SCOPE_PAGE_OPENED
|
||||||
|
ONBOARDING_SETUP_PAGE_OPENED
|
||||||
|
ONBOARDING_TERMS_OF_USE_PAGE_OPENED
|
||||||
|
ONBOARDING_USAGE_MODE_PAGE_OPENED
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Protobuf Enum Numbers (from extension.js)
|
## Protobuf Enum Numbers (from extension.js)
|
||||||
|
|
||||||
Extracted from the compiled protobuf enum definitions in `extension.js`.
|
Extracted from the compiled protobuf enum definitions in `extension.js`.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# ZeroGravity — Linux setup
|
# ZeroGravity — Linux setup
|
||||||
# Creates the zerogravity-ls system user for UID-scoped iptables isolation,
|
# Checks prerequisites, creates the zerogravity-ls system user for
|
||||||
# installs the systemd user service, and builds the dns_redirect.so preload lib.
|
# UID-scoped iptables isolation, and builds the release binary.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
@@ -18,7 +18,26 @@ if [ ${#MISSING[@]} -gt 0 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── 1. System user for UID isolation ──
|
# ── 1. Prerequisite check: Antigravity must be installed ──
|
||||||
|
LS_BINARY="${ZEROGRAVITY_LS_PATH:-/usr/share/antigravity/resources/app/extensions/antigravity/bin/language_server_linux_x64}"
|
||||||
|
echo "→ Checking for Antigravity installation…"
|
||||||
|
if [ ! -f "$LS_BINARY" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "✗ Antigravity is not installed (or the LS binary is missing)."
|
||||||
|
echo " ZeroGravity requires a working Antigravity installation."
|
||||||
|
echo " The Language Server binary is bundled with the Antigravity app"
|
||||||
|
echo " and cannot be downloaded separately."
|
||||||
|
echo ""
|
||||||
|
echo " Expected path:"
|
||||||
|
echo " $LS_BINARY"
|
||||||
|
echo ""
|
||||||
|
echo " Install Antigravity first, then re-run this script."
|
||||||
|
echo " Alternatively, set ZEROGRAVITY_LS_PATH to a custom LS binary location."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " Found: $LS_BINARY"
|
||||||
|
|
||||||
|
# ── 2. System user for UID isolation ──
|
||||||
echo "→ Creating zerogravity-ls system user…"
|
echo "→ Creating zerogravity-ls system user…"
|
||||||
if id -u zerogravity-ls &>/dev/null; then
|
if id -u zerogravity-ls &>/dev/null; then
|
||||||
echo " Already exists."
|
echo " Already exists."
|
||||||
@@ -27,7 +46,7 @@ else
|
|||||||
echo " Created."
|
echo " Created."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── 2. Sudoers rule (run commands as zerogravity-ls without password) ──
|
# ── 3. Sudoers rule (run commands as zerogravity-ls without password) ──
|
||||||
SUDOERS="/etc/sudoers.d/zerogravity"
|
SUDOERS="/etc/sudoers.d/zerogravity"
|
||||||
echo "→ Installing sudoers rule…"
|
echo "→ Installing sudoers rule…"
|
||||||
if [ -f "$SUDOERS" ]; then
|
if [ -f "$SUDOERS" ]; then
|
||||||
@@ -38,41 +57,15 @@ else
|
|||||||
echo " Installed: $SUDOERS"
|
echo " Installed: $SUDOERS"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── 3. Data directory permissions ──
|
# ── 4. Data directory permissions ──
|
||||||
echo "→ Setting up /tmp/zerogravity-standalone…"
|
echo "→ Setting up /tmp/zerogravity-standalone…"
|
||||||
sudo mkdir -p /tmp/zerogravity-standalone
|
sudo mkdir -p /tmp/zerogravity-standalone
|
||||||
sudo chmod 1777 /tmp/zerogravity-standalone
|
sudo chmod 1777 /tmp/zerogravity-standalone
|
||||||
|
|
||||||
# ── 4. Config directory ──
|
# ── 5. Config directory ──
|
||||||
echo "→ Setting up ~/.config/zerogravity…"
|
echo "→ Setting up ~/.config/zerogravity…"
|
||||||
mkdir -p "$HOME/.config/zerogravity"
|
mkdir -p "$HOME/.config/zerogravity"
|
||||||
|
|
||||||
# ── 5. Systemd user service ──
|
|
||||||
echo "→ Installing systemd user service…"
|
|
||||||
UNIT_DIR="$HOME/.config/systemd/user"
|
|
||||||
mkdir -p "$UNIT_DIR"
|
|
||||||
cat > "$UNIT_DIR/zerogravity.service" << EOF
|
|
||||||
[Unit]
|
|
||||||
Description=ZeroGravity Proxy
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=$PROJECT_DIR/target/release/zerogravity
|
|
||||||
WorkingDirectory=$PROJECT_DIR
|
|
||||||
Environment=RUST_LOG=info
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=3
|
|
||||||
StandardOutput=journal
|
|
||||||
StandardError=journal
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
||||||
EOF
|
|
||||||
systemctl --user daemon-reload
|
|
||||||
echo " Installed: $UNIT_DIR/zerogravity.service"
|
|
||||||
echo " Enable with: systemctl --user enable zerogravity"
|
|
||||||
|
|
||||||
# ── 6. Build ──
|
# ── 6. Build ──
|
||||||
echo "→ Building release binary…"
|
echo "→ Building release binary…"
|
||||||
cd "$PROJECT_DIR"
|
cd "$PROJECT_DIR"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# ZeroGravity — macOS setup
|
# ZeroGravity — macOS setup
|
||||||
# Installs a launchd plist for automatic startup and sets up config directories.
|
# Checks prerequisites, sets up config directories, and builds the release binary.
|
||||||
# No UID isolation on macOS — runs in headless/HTTPS_PROXY mode only.
|
# No UID isolation on macOS — runs in headless/HTTPS_PROXY mode only.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -16,50 +16,37 @@ mkdir -p "$CONFIG_DIR"
|
|||||||
echo "→ Setting up /tmp/zerogravity-standalone…"
|
echo "→ Setting up /tmp/zerogravity-standalone…"
|
||||||
mkdir -p /tmp/zerogravity-standalone
|
mkdir -p /tmp/zerogravity-standalone
|
||||||
|
|
||||||
# ── 3. Launchd plist ──
|
# ── 3. Prerequisite check: Antigravity must be installed ──
|
||||||
echo "→ Installing launchd plist…"
|
LS_BINARY="${ZEROGRAVITY_LS_PATH:-}"
|
||||||
PLIST_DIR="$HOME/Library/LaunchAgents"
|
if [ -z "$LS_BINARY" ]; then
|
||||||
PLIST="$PLIST_DIR/com.zerogravity.proxy.plist"
|
# Check both /Applications and ~/Applications
|
||||||
mkdir -p "$PLIST_DIR"
|
for base in "/Applications/Antigravity.app" "$HOME/Applications/Antigravity.app"; do
|
||||||
cat > "$PLIST" << EOF
|
candidate="$base/Contents/Resources/app/extensions/antigravity/bin/language_server_darwin_arm64"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
if [ -f "$candidate" ]; then
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
LS_BINARY="$candidate"
|
||||||
<plist version="1.0">
|
break
|
||||||
<dict>
|
fi
|
||||||
<key>Label</key>
|
done
|
||||||
<string>com.zerogravity.proxy</string>
|
fi
|
||||||
<key>ProgramArguments</key>
|
echo "→ Checking for Antigravity installation…"
|
||||||
<array>
|
if [ -z "$LS_BINARY" ] || [ ! -f "$LS_BINARY" ]; then
|
||||||
<string>$PROJECT_DIR/target/release/zerogravity</string>
|
echo ""
|
||||||
</array>
|
echo "✗ Antigravity is not installed (or the LS binary is missing)."
|
||||||
<key>WorkingDirectory</key>
|
echo " ZeroGravity requires a working Antigravity installation."
|
||||||
<string>$PROJECT_DIR</string>
|
echo " The Language Server binary is bundled with the Antigravity app"
|
||||||
<key>EnvironmentVariables</key>
|
echo " and cannot be downloaded separately."
|
||||||
<dict>
|
echo ""
|
||||||
<key>RUST_LOG</key>
|
echo " Expected in: /Applications/Antigravity.app or ~/Applications/Antigravity.app"
|
||||||
<string>info</string>
|
echo ""
|
||||||
</dict>
|
echo " Install Antigravity first, then re-run this script."
|
||||||
<key>KeepAlive</key>
|
echo " Alternatively, set ZEROGRAVITY_LS_PATH to a custom LS binary location."
|
||||||
<dict>
|
exit 1
|
||||||
<key>SuccessfulExit</key>
|
fi
|
||||||
<false/>
|
echo " Found: $LS_BINARY"
|
||||||
</dict>
|
|
||||||
<key>StandardOutPath</key>
|
|
||||||
<string>$HOME/Library/Logs/zerogravity.log</string>
|
|
||||||
<key>StandardErrorPath</key>
|
|
||||||
<string>$HOME/Library/Logs/zerogravity.log</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
EOF
|
|
||||||
echo " Installed: $PLIST"
|
|
||||||
echo " Start with: launchctl load $PLIST"
|
|
||||||
echo " Stop with: launchctl unload $PLIST"
|
|
||||||
|
|
||||||
# ── 4. Build ──
|
# ── 4. Build ──
|
||||||
echo "→ Building release binary…"
|
echo "→ Building release binary…"
|
||||||
cd "$PROJECT_DIR"
|
cd "$PROJECT_DIR"
|
||||||
cargo build --release 2>&1 | tail -1
|
cargo build --release 2>&1 | tail -1
|
||||||
echo ""
|
echo ""
|
||||||
echo "✓ Setup complete."
|
echo "✓ Setup complete. Start with: zg start"
|
||||||
echo " Start with: launchctl load $PLIST"
|
|
||||||
echo " Or manually: zg start"
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# ZeroGravity — Windows setup
|
# ZeroGravity — Windows setup
|
||||||
# Creates config directories, builds the release binary, and optionally
|
# Creates config directories, checks prerequisites, and builds the release binary.
|
||||||
# installs as a scheduled task for automatic startup.
|
|
||||||
# Run as: powershell -ExecutionPolicy Bypass -File scripts\setup-windows.ps1
|
# Run as: powershell -ExecutionPolicy Bypass -File scripts\setup-windows.ps1
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
@@ -19,7 +18,26 @@ $DataDir = Join-Path $env:TEMP "zerogravity-standalone"
|
|||||||
New-Item -ItemType Directory -Force -Path $DataDir | Out-Null
|
New-Item -ItemType Directory -Force -Path $DataDir | Out-Null
|
||||||
Write-Host " $DataDir"
|
Write-Host " $DataDir"
|
||||||
|
|
||||||
# ── 3. Build ──
|
# ── 3. Prerequisite check: Antigravity must be installed ──
|
||||||
|
Write-Host "→ Checking for Antigravity installation…"
|
||||||
|
$LsBinary = Join-Path $env:LOCALAPPDATA "Programs\Antigravity\resources\app\extensions\antigravity\bin\language_server_windows_x64.exe"
|
||||||
|
if (-not (Test-Path $LsBinary)) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "✗ Antigravity is not installed (or the LS binary is missing)." -ForegroundColor Red
|
||||||
|
Write-Host " ZeroGravity requires a working Antigravity installation."
|
||||||
|
Write-Host " The Language Server binary is bundled with the Antigravity app"
|
||||||
|
Write-Host " and cannot be downloaded separately."
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " Expected path:"
|
||||||
|
Write-Host " $LsBinary"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " Install Antigravity first, then re-run this script."
|
||||||
|
Write-Host " Alternatively, set ZEROGRAVITY_LS_PATH to a custom LS binary location."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Host " Found: $LsBinary"
|
||||||
|
|
||||||
|
# ── 4. Build ──
|
||||||
Write-Host "→ Building release binary…"
|
Write-Host "→ Building release binary…"
|
||||||
Push-Location $ProjectDir
|
Push-Location $ProjectDir
|
||||||
cargo build --release
|
cargo build --release
|
||||||
@@ -32,37 +50,6 @@ if (-not (Test-Path $Binary)) {
|
|||||||
}
|
}
|
||||||
Write-Host " Built: $Binary"
|
Write-Host " Built: $Binary"
|
||||||
|
|
||||||
# ── 4. Scheduled task (optional) ──
|
|
||||||
$TaskName = "ZeroGravity Proxy"
|
|
||||||
$Existing = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
|
||||||
|
|
||||||
if ($Existing) {
|
|
||||||
Write-Host "→ Scheduled task '$TaskName' already exists. Updating…"
|
|
||||||
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "→ Creating scheduled task '$TaskName'…"
|
|
||||||
$Action = New-ScheduledTaskAction `
|
|
||||||
-Execute $Binary `
|
|
||||||
-WorkingDirectory $ProjectDir
|
|
||||||
|
|
||||||
$Trigger = New-ScheduledTaskTrigger -AtLogOn
|
|
||||||
$Settings = New-ScheduledTaskSettingsSet `
|
|
||||||
-AllowStartIfOnBatteries `
|
|
||||||
-DontStopIfGoingOnBatteries `
|
|
||||||
-RestartCount 3 `
|
|
||||||
-RestartInterval (New-TimeSpan -Minutes 1)
|
|
||||||
|
|
||||||
Register-ScheduledTask `
|
|
||||||
-TaskName $TaskName `
|
|
||||||
-Action $Action `
|
|
||||||
-Trigger $Trigger `
|
|
||||||
-Settings $Settings `
|
|
||||||
-Description "ZeroGravity OpenAI-compatible proxy" | Out-Null
|
|
||||||
|
|
||||||
Write-Host " Installed as logon task."
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "✓ Setup complete."
|
Write-Host "✓ Setup complete."
|
||||||
Write-Host " Start now: schtasks /run /tn '$TaskName'"
|
Write-Host " Start: .\target\release\zerogravity.exe"
|
||||||
Write-Host " Stop: schtasks /end /tn '$TaskName'"
|
|
||||||
Write-Host " Or manually: .\target\release\zerogravity.exe"
|
|
||||||
|
|||||||
@@ -325,9 +325,7 @@ fn svc_stop() -> bool {
|
|||||||
}
|
}
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
let _ = Command::new("pkill")
|
let _ = Command::new("pkill").args(["-f", "zerogravity"]).status();
|
||||||
.args(["-f", "zerogravity"])
|
|
||||||
.status();
|
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -517,9 +515,8 @@ fn do_test(msg: &str) {
|
|||||||
.replace('\n', "\\n")
|
.replace('\n', "\\n")
|
||||||
.replace('\r', "\\r")
|
.replace('\r', "\\r")
|
||||||
.replace('\t', "\\t");
|
.replace('\t', "\\t");
|
||||||
let body = format!(
|
let body =
|
||||||
r#"{{"model":"gemini-3-flash","input":"{escaped}","stream":false,"timeout":30}}"#
|
format!(r#"{{"model":"gemini-3-flash","input":"{escaped}","stream":false,"timeout":30}}"#);
|
||||||
);
|
|
||||||
match curl_post("/v1/responses", &body) {
|
match curl_post("/v1/responses", &body) {
|
||||||
Some(json) => jq_print(&json),
|
Some(json) => jq_print(&json),
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
@@ -42,15 +42,15 @@ use tracing::{debug, info, trace, warn};
|
|||||||
/// We mirror this by maintaining a single upstream connection per domain.
|
/// We mirror this by maintaining a single upstream connection per domain.
|
||||||
struct UpstreamPool {
|
struct UpstreamPool {
|
||||||
domain: String,
|
domain: String,
|
||||||
tls_config: Arc<rustls::ClientConfig>,
|
tls_connector: boring2::ssl::SslConnector,
|
||||||
sender: Mutex<Option<hyper::client::conn::http2::SendRequest<Full<Bytes>>>>,
|
sender: Mutex<Option<hyper::client::conn::http2::SendRequest<Full<Bytes>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpstreamPool {
|
impl UpstreamPool {
|
||||||
fn new(domain: String, tls_config: Arc<rustls::ClientConfig>) -> Self {
|
fn new(domain: String, tls_connector: boring2::ssl::SslConnector) -> Self {
|
||||||
Self {
|
Self {
|
||||||
domain,
|
domain,
|
||||||
tls_config,
|
tls_connector,
|
||||||
sender: Mutex::new(None),
|
sender: Mutex::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,17 +82,29 @@ impl UpstreamPool {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("upstream TCP connect to {} failed: {e}", self.domain))?;
|
.map_err(|e| format!("upstream TCP connect to {} failed: {e}", self.domain))?;
|
||||||
|
|
||||||
let connector = tokio_rustls::TlsConnector::from(self.tls_config.clone());
|
let ssl = self
|
||||||
let server_name = rustls::pki_types::ServerName::try_from(self.domain.clone())
|
.tls_connector
|
||||||
.map_err(|e| format!("invalid domain {}: {e}", self.domain))?;
|
.configure()
|
||||||
|
.map_err(|e| format!("SSL configure: {e}"))?
|
||||||
|
.into_ssl(&self.domain)
|
||||||
|
.map_err(|e| format!("SSL into_ssl: {e}"))?;
|
||||||
|
|
||||||
let upstream_tls = connector
|
let mut upstream_tls = tokio_boring2::SslStream::new(ssl, upstream_tcp)
|
||||||
.connect(server_name, upstream_tcp)
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("upstream TLS to {} failed: {e}", self.domain))?;
|
.map_err(|e| format!("upstream TLS to {} failed: {e}", self.domain))?;
|
||||||
|
|
||||||
|
std::pin::Pin::new(&mut upstream_tls)
|
||||||
|
.connect()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("TLS handshake to {} failed: {e}", self.domain))?;
|
||||||
|
|
||||||
let upstream_io = TokioIo::new(upstream_tls);
|
let upstream_io = TokioIo::new(upstream_tls);
|
||||||
|
// Configure HTTP/2 SETTINGS to match Go's net/http2 defaults
|
||||||
|
// Source: golang.org/x/net/http2/transport.go
|
||||||
let (sender, conn) = hyper::client::conn::http2::Builder::new(TokioExecutor::new())
|
let (sender, conn) = hyper::client::conn::http2::Builder::new(TokioExecutor::new())
|
||||||
|
.initial_stream_window_size(4 << 20) // 4MB (Go: transportDefaultStreamFlow)
|
||||||
|
.initial_connection_window_size(1 << 30) // 1GB (Go: transportDefaultConnFlow)
|
||||||
|
.max_header_list_size(10 * 1024 * 1024) // 10MB (Go: defaultMaxHeaderListSize)
|
||||||
|
.adaptive_window(false) // Go doesn't use adaptive windowing
|
||||||
.handshake(upstream_io)
|
.handshake(upstream_io)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("upstream h2 handshake to {} failed: {e}", self.domain))?;
|
.map_err(|e| format!("upstream h2 handshake to {} failed: {e}", self.domain))?;
|
||||||
@@ -140,22 +152,11 @@ where
|
|||||||
{
|
{
|
||||||
info!(domain = %domain, "MITM H2: handling HTTP/2 connection");
|
info!(domain = %domain, "MITM H2: handling HTTP/2 connection");
|
||||||
|
|
||||||
// Build TLS config for upstream connections
|
// Build upstream TLS connector matching Go's crypto/tls fingerprint (with ALPN h2)
|
||||||
let mut root_store = rustls::RootCertStore::empty();
|
let upstream_connector = super::tls::build_go_tls_connector(Some(&[b"h2"]));
|
||||||
let native_certs = rustls_native_certs::load_native_certs();
|
|
||||||
for cert in native_certs.certs {
|
|
||||||
let _ = root_store.add(cert);
|
|
||||||
}
|
|
||||||
let mut upstream_tls_config = rustls::ClientConfig::builder()
|
|
||||||
.with_root_certificates(root_store)
|
|
||||||
.with_no_client_auth();
|
|
||||||
upstream_tls_config.alpn_protocols = vec![b"h2".to_vec()];
|
|
||||||
|
|
||||||
// Shared upstream connection pool (single connection, multiplexed)
|
// Shared upstream connection pool (single connection, multiplexed)
|
||||||
let pool = Arc::new(UpstreamPool::new(
|
let pool = Arc::new(UpstreamPool::new(domain.clone(), upstream_connector));
|
||||||
domain.clone(),
|
|
||||||
Arc::new(upstream_tls_config),
|
|
||||||
));
|
|
||||||
|
|
||||||
let io = TokioIo::new(tls_stream);
|
let io = TokioIo::new(tls_stream);
|
||||||
let domain = Arc::new(domain);
|
let domain = Arc::new(domain);
|
||||||
|
|||||||
@@ -18,3 +18,4 @@ pub mod modify;
|
|||||||
pub mod proto;
|
pub mod proto;
|
||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
pub mod tls;
|
||||||
|
|||||||
@@ -368,20 +368,11 @@ async fn handle_http_over_tls(
|
|||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut tmp = vec![0u8; 32768];
|
let mut tmp = vec![0u8; 32768];
|
||||||
|
|
||||||
// Build upstream TLS connector once for this connection
|
// Build upstream TLS connector matching Go's crypto/tls fingerprint
|
||||||
let mut root_store = rustls::RootCertStore::empty();
|
let upstream_connector = super::tls::build_go_tls_connector(None);
|
||||||
let native_certs = rustls_native_certs::load_native_certs();
|
|
||||||
for cert in native_certs.certs {
|
|
||||||
let _ = root_store.add(cert);
|
|
||||||
}
|
|
||||||
let upstream_config = Arc::new(
|
|
||||||
rustls::ClientConfig::builder()
|
|
||||||
.with_root_certificates(root_store)
|
|
||||||
.with_no_client_auth(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reusable upstream connection — created lazily, reconnected if stale
|
// Reusable upstream connection — created lazily, reconnected if stale
|
||||||
let mut upstream: Option<tokio_rustls::client::TlsStream<TcpStream>> = None;
|
let mut upstream: Option<tokio_boring2::SslStream<TcpStream>> = None;
|
||||||
|
|
||||||
// Keep-alive loop: handle multiple requests on this connection
|
// Keep-alive loop: handle multiple requests on this connection
|
||||||
loop {
|
loop {
|
||||||
@@ -575,7 +566,7 @@ async fn handle_http_over_tls(
|
|||||||
let conn = match upstream.as_mut() {
|
let conn = match upstream.as_mut() {
|
||||||
Some(c) => c,
|
Some(c) => c,
|
||||||
None => {
|
None => {
|
||||||
let c = connect_upstream(domain, &upstream_config).await?;
|
let c = connect_upstream(domain, &upstream_connector).await?;
|
||||||
upstream.insert(c)
|
upstream.insert(c)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -583,7 +574,7 @@ async fn handle_http_over_tls(
|
|||||||
// Forward the request — if write fails, reconnect and retry once
|
// Forward the request — if write fails, reconnect and retry once
|
||||||
if let Err(e) = conn.write_all(&request_buf).await {
|
if let Err(e) = conn.write_all(&request_buf).await {
|
||||||
debug!(domain, error = %e, "MITM: upstream write failed, reconnecting");
|
debug!(domain, error = %e, "MITM: upstream write failed, reconnecting");
|
||||||
let c = connect_upstream(domain, &upstream_config).await?;
|
let c = connect_upstream(domain, &upstream_connector).await?;
|
||||||
let conn = upstream.insert(c);
|
let conn = upstream.insert(c);
|
||||||
conn.write_all(&request_buf)
|
conn.write_all(&request_buf)
|
||||||
.await
|
.await
|
||||||
@@ -905,15 +896,15 @@ async fn read_full_request(
|
|||||||
|
|
||||||
/// Connect (or reconnect) to the real upstream via TLS.
|
/// Connect (or reconnect) to the real upstream via TLS.
|
||||||
///
|
///
|
||||||
|
/// Uses BoringSSL configured to match Go's `crypto/tls` fingerprint.
|
||||||
/// Bypasses /etc/hosts by resolving via direct DNS query (dig @8.8.8.8),
|
/// Bypasses /etc/hosts by resolving via direct DNS query (dig @8.8.8.8),
|
||||||
/// then falls back to cached IPs file, then to normal system resolution.
|
/// then falls back to cached IPs file, then to normal system resolution.
|
||||||
async fn connect_upstream(
|
async fn connect_upstream(
|
||||||
domain: &str,
|
domain: &str,
|
||||||
config: &Arc<rustls::ClientConfig>,
|
connector: &boring2::ssl::SslConnector,
|
||||||
) -> Result<tokio_rustls::client::TlsStream<TcpStream>, String> {
|
) -> Result<tokio_boring2::SslStream<TcpStream>, String> {
|
||||||
let connector = tokio_rustls::TlsConnector::from(config.clone());
|
|
||||||
let addr = resolve_upstream(domain).await;
|
let addr = resolve_upstream(domain).await;
|
||||||
info!(domain, addr = %addr, "MITM: connecting upstream");
|
info!(domain, addr = %addr, "MITM: connecting upstream (BoringSSL)");
|
||||||
|
|
||||||
let tcp = match tokio::time::timeout(
|
let tcp = match tokio::time::timeout(
|
||||||
std::time::Duration::from_secs(15),
|
std::time::Duration::from_secs(15),
|
||||||
@@ -926,20 +917,26 @@ async fn connect_upstream(
|
|||||||
Err(_) => return Err(format!("Connect to upstream {domain} ({addr}): timed out")),
|
Err(_) => return Err(format!("Connect to upstream {domain} ({addr}): timed out")),
|
||||||
};
|
};
|
||||||
|
|
||||||
let server_name = rustls::pki_types::ServerName::try_from(domain.to_string())
|
let ssl = connector
|
||||||
.map_err(|e| format!("Invalid server name: {e}"))?;
|
.configure()
|
||||||
|
.map_err(|e| format!("SSL configure: {e}"))?
|
||||||
|
.into_ssl(domain)
|
||||||
|
.map_err(|e| format!("SSL into_ssl: {e}"))?;
|
||||||
|
|
||||||
|
let mut stream = tokio_boring2::SslStream::new(ssl, tcp)
|
||||||
|
.map_err(|e| format!("SslStream::new for {domain}: {e}"))?;
|
||||||
|
|
||||||
match tokio::time::timeout(
|
match tokio::time::timeout(
|
||||||
std::time::Duration::from_secs(15),
|
std::time::Duration::from_secs(15),
|
||||||
connector.connect(server_name, tcp),
|
std::pin::Pin::new(&mut stream).connect(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(Ok(s)) => {
|
Ok(Ok(())) => {
|
||||||
info!(domain, "MITM: upstream TLS connected ✓");
|
info!(domain, "MITM: upstream TLS connected ✓ (BoringSSL)");
|
||||||
Ok(s)
|
Ok(stream)
|
||||||
}
|
}
|
||||||
Ok(Err(e)) => Err(format!("TLS connect to upstream {domain}: {e}")),
|
Ok(Err(e)) => Err(format!("TLS handshake to upstream {domain}: {e}")),
|
||||||
Err(_) => Err(format!("TLS connect to upstream {domain}: timed out")),
|
Err(_) => Err(format!("TLS connect to upstream {domain}: timed out")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
86
src/mitm/tls.rs
Normal file
86
src/mitm/tls.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
//! Upstream TLS configuration matching Go's `crypto/tls` defaults.
|
||||||
|
//!
|
||||||
|
//! The LS is a Go binary — its outbound TLS to Google uses Go's default
|
||||||
|
//! cipher suites, curves, and signature algorithms. This module configures
|
||||||
|
//! BoringSSL to produce a matching TLS ClientHello so Google sees the same
|
||||||
|
//! JA3/JA4 fingerprint regardless of whether our MITM is active.
|
||||||
|
|
||||||
|
use boring2::ssl::{SslConnector, SslMethod};
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
|
/// Go's default cipher suites in the exact order Go's `crypto/tls` sends them.
|
||||||
|
///
|
||||||
|
/// TLS 1.3 ciphers (hardcoded in Go, not configurable):
|
||||||
|
/// TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256
|
||||||
|
///
|
||||||
|
/// TLS 1.2 ciphers (Go's default preference order):
|
||||||
|
/// ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-GCM-SHA256,
|
||||||
|
/// ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-GCM-SHA384,
|
||||||
|
/// ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305,
|
||||||
|
/// ECDHE-RSA-AES128-SHA, ECDHE-RSA-AES256-SHA,
|
||||||
|
/// AES128-GCM-SHA256, AES256-GCM-SHA384, AES128-SHA, AES256-SHA
|
||||||
|
const GO_CIPHER_LIST: &str = "\
|
||||||
|
TLS_AES_128_GCM_SHA256:\
|
||||||
|
TLS_AES_256_GCM_SHA384:\
|
||||||
|
TLS_CHACHA20_POLY1305_SHA256:\
|
||||||
|
ECDHE-ECDSA-AES128-GCM-SHA256:\
|
||||||
|
ECDHE-RSA-AES128-GCM-SHA256:\
|
||||||
|
ECDHE-ECDSA-AES256-GCM-SHA384:\
|
||||||
|
ECDHE-RSA-AES256-GCM-SHA384:\
|
||||||
|
ECDHE-ECDSA-CHACHA20-POLY1305:\
|
||||||
|
ECDHE-RSA-CHACHA20-POLY1305:\
|
||||||
|
ECDHE-RSA-AES128-SHA:\
|
||||||
|
ECDHE-RSA-AES256-SHA:\
|
||||||
|
AES128-GCM-SHA256:\
|
||||||
|
AES256-GCM-SHA384:\
|
||||||
|
AES128-SHA:\
|
||||||
|
AES256-SHA";
|
||||||
|
|
||||||
|
/// Go's default signature algorithms.
|
||||||
|
const GO_SIGALGS: &str = "\
|
||||||
|
ECDSA+SHA256:\
|
||||||
|
RSA-PSS+SHA256:\
|
||||||
|
RSA+SHA256:\
|
||||||
|
ECDSA+SHA384:\
|
||||||
|
RSA-PSS+SHA384:\
|
||||||
|
RSA+SHA384:\
|
||||||
|
RSA-PSS+SHA512:\
|
||||||
|
RSA+SHA512:\
|
||||||
|
RSA+SHA1";
|
||||||
|
|
||||||
|
/// Build an `SslConnector` that mimics Go's `crypto/tls` defaults.
|
||||||
|
///
|
||||||
|
/// If `alpn` is provided, sets ALPN protocols (e.g. `&[b"h2"]` for HTTP/2).
|
||||||
|
pub fn build_go_tls_connector(alpn: Option<&[&[u8]]>) -> SslConnector {
|
||||||
|
let mut builder =
|
||||||
|
SslConnector::builder(SslMethod::tls_client()).expect("SslConnector::builder");
|
||||||
|
|
||||||
|
// Set Go's cipher list
|
||||||
|
builder
|
||||||
|
.set_cipher_list(GO_CIPHER_LIST)
|
||||||
|
.expect("set_cipher_list");
|
||||||
|
|
||||||
|
// Set Go's signature algorithms
|
||||||
|
builder
|
||||||
|
.set_sigalgs_list(GO_SIGALGS)
|
||||||
|
.expect("set_sigalgs_list");
|
||||||
|
|
||||||
|
// Set Go's default curves: X25519, P-256, P-384
|
||||||
|
// BoringSSL uses set_curves_list with colon-separated names
|
||||||
|
builder
|
||||||
|
.set_curves_list("X25519:P-256:P-384")
|
||||||
|
.expect("set_curves_list");
|
||||||
|
|
||||||
|
// ALPN if requested (for HTTP/2)
|
||||||
|
if let Some(protos) = alpn {
|
||||||
|
let mut wire = Vec::new();
|
||||||
|
for proto in protos {
|
||||||
|
wire.push(proto.len() as u8);
|
||||||
|
wire.extend_from_slice(proto);
|
||||||
|
}
|
||||||
|
builder.set_alpn_protos(&wire).expect("set_alpn_protos");
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Built Go-matching TLS connector (BoringSSL)");
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
@@ -275,22 +275,32 @@ impl StandaloneLS {
|
|||||||
// With iptables, all outbound traffic is transparently redirected at the
|
// With iptables, all outbound traffic is transparently redirected at the
|
||||||
// kernel level — setting HTTPS_PROXY on top causes double-proxying.
|
// kernel level — setting HTTPS_PROXY on top causes double-proxying.
|
||||||
if headless || !platform::supports_uid_isolation() {
|
if headless || !platform::supports_uid_isolation() {
|
||||||
// proxy_addr already includes the scheme (e.g. "http://127.0.0.1:8742")
|
|
||||||
env_vars.push(("HTTPS_PROXY".into(), mitm.proxy_addr.clone()));
|
|
||||||
env_vars.push(("HTTP_PROXY".into(), mitm.proxy_addr.clone()));
|
|
||||||
|
|
||||||
// LD_PRELOAD DNS redirect: hooks getaddrinfo() so Google API domains
|
// LD_PRELOAD DNS redirect: hooks getaddrinfo() so Google API domains
|
||||||
// resolve to 127.0.0.1. Combined with the port-modified endpoint URL,
|
// resolve to 127.0.0.1. Combined with the port-modified endpoint URL,
|
||||||
// this makes the LS connect to our MITM proxy for ALL API calls —
|
// this makes the LS connect to our MITM proxy for ALL API calls —
|
||||||
// even the CodeAssistClient which has Proxy:nil hardcoded.
|
// even the CodeAssistClient which has Proxy:nil hardcoded.
|
||||||
let so_path = build_dns_redirect_so();
|
let so_path = build_dns_redirect_so();
|
||||||
if let Some(so) = so_path {
|
if let Some(ref so) = so_path {
|
||||||
info!(path = %so, "Enabling LD_PRELOAD DNS redirect for headless MITM");
|
info!(path = %so, "Enabling LD_PRELOAD DNS redirect for headless MITM");
|
||||||
env_vars.push(("LD_PRELOAD".into(), so));
|
env_vars.push(("LD_PRELOAD".into(), so.clone()));
|
||||||
env_vars.push((
|
env_vars.push((
|
||||||
"DNS_REDIRECT_LOG".into(),
|
"DNS_REDIRECT_LOG".into(),
|
||||||
format!("{data_dir}/dns-redirect.log"),
|
format!("{data_dir}/dns-redirect.log"),
|
||||||
));
|
));
|
||||||
|
// Force Go binaries to use cgo (libc) DNS resolver instead of
|
||||||
|
// the pure-Go resolver. Without this, LD_PRELOAD getaddrinfo()
|
||||||
|
// hooks are bypassed because Go resolves DNS internally.
|
||||||
|
env_vars.push(("GODEBUG".into(), "netdns=cgo".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only set HTTPS_PROXY as fallback when DNS redirect is NOT available.
|
||||||
|
// When DNS redirect IS active, HTTPS_PROXY is redundant and harmful:
|
||||||
|
// Go's net/http sends HTTP CONNECT through the proxy, but the MITM
|
||||||
|
// proxy expects direct TLS connections (SNI-based interception).
|
||||||
|
// This causes OAuth token refresh and other non-gRPC calls to fail.
|
||||||
|
if so_path.is_none() {
|
||||||
|
env_vars.push(("HTTPS_PROXY".into(), mitm.proxy_addr.clone()));
|
||||||
|
env_vars.push(("HTTP_PROXY".into(), mitm.proxy_addr.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user