Skip to content

feat: add Larri socket secrets provider#164

Closed
larimonious wants to merge 10 commits into
mainfrom
feat/secrets-socket-provider
Closed

feat: add Larri socket secrets provider#164
larimonious wants to merge 10 commits into
mainfrom
feat/secrets-socket-provider

Conversation

@larimonious

Copy link
Copy Markdown
Contributor

Summary

  • add a Unix-only Larri host-agent provider for std/secrets
  • define a strict, versioned, request-correlated newline JSON protocol with scope validation
  • bound connect, write, read, frame, value, endpoint, and retry behavior
  • fail closed on malformed frames, ID/scope mismatches, unsafe paths, and terminal agent results
  • keep plaintext outage-grace caching in the host agent rather than ntnt
  • document deployment environment variables and regenerate runtime/stdlib references

Security properties

  • ntnt remains provider-neutral and has no Infisical dependency
  • requests carry only protocol version, non-credential request ID, operation, and declared logical name
  • production endpoints must be beneath /run/larri-secrets; final files and parent components are checked against symlink/path substitution
  • response buffers and decoded values use zeroizing storage
  • diagnostics use safe ordinal endpoint labels and never raw socket paths, scopes, response fragments, or backend errors
  • only unavailable retries/fails over; missing, denied, invalid request/configuration, and malformed responses are terminal

Verification

  • cargo test --all-targets
  • cargo build --profile dev-release
  • cargo fmt --all -- --check
  • cargo run -- docs --validate
  • generated docs are idempotent
  • all examples validate; example lint has zero errors and only existing warnings/suggestions
  • full Clippy remains blocked by existing approximate-constant errors outside this diff; changed-line diagnostic filter reports zero findings
  • independent implementation review: approved

Platform note

The provider implementation and socket fixtures are Unix-gated. Local cross-target checks cannot link the installed macOS/Windows targets because their external cross-linkers/toolchains are unavailable; native Linux checks pass and CI remains the cross-platform authority.

@larimonious

Copy link
Copy Markdown
Contributor Author

@greptileai review

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a Unix-only Larri socket secrets provider for std/secrets, implementing a strictly versioned, request-correlated newline-JSON protocol over Unix domain sockets. Provider-group failover, retry semantics, and terminal/transient error classification are unchanged; only Unavailable advances to the next endpoint.

  • socket.rs: new SocketSecretProvider with deadline-bounded connect/write/read, a 64 KiB frame limit, CR rejection, single-newline framing, non-blocking EOF confirmation, and Zeroizing<String> response storage. Response headers (protocol version, request_id, scope) are validated before any status-specific logic executes.
  • secrets.rs: adds ProviderEndpointLabel for opaque diagnostic labels, InvalidRequest terminal error kind, parse_socket_provider_config with multi-layer path validation, and production-mode enforcement that restricts paths to beneath /run/larri-secrets.
  • secret.rs / Cargo.toml: SecretValue::new_zeroizing avoids copying secret bytes through a plain String at the provider boundary; the serde feature is enabled on zeroize to allow direct deserialization into Zeroizing<String> for the Found response variant.

Confidence Score: 5/5

The change is safe to merge; all identified concerns from previous review rounds are tracked in open threads and the new logic is well-guarded with both unit and integration tests.

The socket provider is a self-contained addition with no changes to existing provider semantics or the public API. Path validation, symlink checks, framing, scope enforcement, and secret zeroization are all layered and tested. No new correctness or security defects were identified in this pass.

src/stdlib/secrets/socket.rs — three open discussions from prior review rounds (empty-accept classification, serde deny_unknown_fields with internal tagging, EOF confirmation error kind) are still pending resolution.

Important Files Changed

Filename Overview
src/stdlib/secrets/socket.rs New Larri Unix socket provider: deadline-bounded connect/write/read, strict frame validation (64 KiB limit, CR rejection, single newline, EOF confirmation), request-ID correlation, scope validation, and zeroizing response storage. Previously flagged threads exist around Ok(0) classification and deny_unknown_fields with internally tagged serde.
src/stdlib/secrets.rs Adds ProviderEndpointLabel, InvalidRequest error kind, Zeroizing wrapping for env provider values, SocketProviderConfig parsing with path validation (abs-path, no traversal, length, control chars, dedup), production enforcement under /run/larri-secrets, and symlink checks at config and connect time.
src/secret.rs Splits SecretValue::new into new() (test-only) and new_zeroizing() to avoid copying secret bytes through a plain String at the provider boundary.
Cargo.toml Enables the serde feature on zeroize 1.8.2 to allow Deserialize for Zeroizing (required for the Found variant's value field).
tests/language_features_tests.rs Minor updates to integration test fixtures; no new logic introduced.
docs/RUNTIME_REFERENCE.md Generated documentation for new env vars (NTNT_SECRETS_PROVIDER, NTNT_SECRETS_SOCKET_ENDPOINTS, NTNT_SECRETS_AUTHORIZATION_SCOPE, NTNT_SECRETS_TIMEOUT_MS); docs-only, regenerated idempotently.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant App as ntnt Application
    participant SS as stdlib/secrets.rs
    participant PG as ProviderGroup
    participant SSP as SocketSecretProvider
    participant FS as Filesystem (symlink/socket checks)
    participant LA as Larri Host Agent (Unix socket)

    App->>SS: get_secret("API_KEY")
    SS->>SS: validate_secret_name()
    SS->>SS: enforce_declared()
    SS->>SS: configured_provider_group() [reads env vars]
    SS->>PG: lookup("API_KEY")

    loop per endpoint (up to 2 attempts each)
        PG->>SSP: lookup("API_KEY")
        SSP->>FS: symlink_metadata(path) [final path check]
        alt production mode
            SSP->>FS: validate_no_symlink_components(path) [parent components]
        end
        SSP->>LA: connect_timeout()
        SSP->>LA: "write JSON request {protocol, request_id, op, name} + LF"
        SSP->>LA: shutdown(Write)
        LA-->>SSP: JSON response + LF (bounded 64 KiB)
        SSP->>SSP: read_response_frame()
        SSP->>SSP: serde_json::from_slice() with deny_unknown_fields
        SSP->>SSP: validate_response_header() [protocol, request_id, scope]

        alt "status = found"
            SSP-->>PG: "ProviderLookup::Found(Zeroizing<String>)"
            PG-->>SS: SecretValue (zeroizing)
            SS-->>App: Some(Secret)
        else "status = unavailable"
            SSP-->>PG: Err(Unavailable) retry / next endpoint
        else "status = missing / access_denied / invalid_request / invalid_configuration"
            SSP-->>PG: Err(terminal) stop immediately
            PG-->>SS: Err(IntentError)
            SS-->>App: Err
        end
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant App as ntnt Application
    participant SS as stdlib/secrets.rs
    participant PG as ProviderGroup
    participant SSP as SocketSecretProvider
    participant FS as Filesystem (symlink/socket checks)
    participant LA as Larri Host Agent (Unix socket)

    App->>SS: get_secret("API_KEY")
    SS->>SS: validate_secret_name()
    SS->>SS: enforce_declared()
    SS->>SS: configured_provider_group() [reads env vars]
    SS->>PG: lookup("API_KEY")

    loop per endpoint (up to 2 attempts each)
        PG->>SSP: lookup("API_KEY")
        SSP->>FS: symlink_metadata(path) [final path check]
        alt production mode
            SSP->>FS: validate_no_symlink_components(path) [parent components]
        end
        SSP->>LA: connect_timeout()
        SSP->>LA: "write JSON request {protocol, request_id, op, name} + LF"
        SSP->>LA: shutdown(Write)
        LA-->>SSP: JSON response + LF (bounded 64 KiB)
        SSP->>SSP: read_response_frame()
        SSP->>SSP: serde_json::from_slice() with deny_unknown_fields
        SSP->>SSP: validate_response_header() [protocol, request_id, scope]

        alt "status = found"
            SSP-->>PG: "ProviderLookup::Found(Zeroizing<String>)"
            PG-->>SS: SecretValue (zeroizing)
            SS-->>App: Some(Secret)
        else "status = unavailable"
            SSP-->>PG: Err(Unavailable) retry / next endpoint
        else "status = missing / access_denied / invalid_request / invalid_configuration"
            SSP-->>PG: Err(terminal) stop immediately
            PG-->>SS: Err(IntentError)
            SS-->>App: Err
        end
    end
Loading

Reviews (9): Last reviewed commit: "fix: validate complete frames without bl..." | Re-trigger Greptile

Comment thread src/stdlib/secrets/socket.rs Outdated
Comment on lines +210 to +211
#[derive(Deserialize)]
#[serde(tag = "status", rename_all = "snake_case", deny_unknown_fields)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 deny_unknown_fields may not be enforced on internally tagged serde enums

The serde documentation notes that deny_unknown_fields combined with #[serde(tag = "...")] (internally tagged representation) has known limitations — unknown fields may fail silently in some serde versions. The "unknown-field" test validates the current behavior, but if serde is upgraded and this guarantee quietly regresses, an agent could include an extra field (e.g., a "details" error string containing backend information) and it would be accepted rather than rejected. It is worth verifying the exact behavior of deny_unknown_fields with serde_json's internally tagged path in the pinned serde version and adding a comment explaining the dependency on this specific behavior.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a Unix-only larri-socket provider for std/secrets that connects to a local Larri host-agent over a Unix domain socket using a strict, versioned, newline-delimited JSON protocol. The implementation avoids any direct Infisical dependency and introduces four new environment variables (NTNT_SECRETS_PROVIDER, NTNT_SECRETS_SOCKET_ENDPOINTS, NTNT_SECRETS_AUTHORIZATION_SCOPE, NTNT_SECRETS_TIMEOUT_MS) to configure endpoints, deployment-scope correlation, and per-attempt timeouts.

  • Protocol and framing: each lookup opens a fresh connection, sends one JSON request, half-closes the write side, reads exactly one bounded (64 KiB) newline-framed response through EOF, and validates the echoed protocol, request_id, and scope before accepting any value; unknown fields, extra frames, and all framing violations fail closed.
  • Security hardening: production paths are required to be strictly beneath /run/larri-secrets with both config-time and connect-time symlink checks on all parent components; secret values are carried in Zeroizing<String> from deserialization through SecretValue; error diagnostics use opaque ordinal endpoint labels and never render paths, scope values, or response fragments.
  • HA semantics: only unavailable results (including connect-time not-found) are retriable/failover-eligible; all other outcomes — missing, denied, invalid request/configuration, and malformed frames — are terminal to prevent scope substitution across endpoints.

Confidence Score: 4/5

Safe to merge; the core security properties (zeroizing storage, scope validation, symlink rejection, frame isolation) are well-implemented and thoroughly covered by unit and integration tests.

The framing logic, scope correlation, and symlink path checks are all correct and test-verified. The one notable design choice worth discussing is that an agent crash after accepting a connection (but before writing any bytes) is classified as a terminal configuration error rather than a transient unavailability, which prevents the retry and failover machinery from reaching other configured endpoints in that failure window. This is explicitly documented behaviour, but it does reduce the practical benefit of multi-endpoint setups during rolling restarts or abrupt agent crashes.

src/stdlib/secrets/socket.rs — specifically the Ok(0) branch in read_response_frame and the trailing-EOF read classification.

Important Files Changed

Filename Overview
src/stdlib/secrets/socket.rs New file implementing the Larri Unix-socket protocol v1. Frame parsing, timeout enforcement, symlink rejection, scope/request-ID correlation, and zeroizing storage are all well-implemented and thoroughly tested. One intentional design choice — classifying an early EOF from a connected agent as terminal InvalidConfiguration rather than retriable Unavailable — may limit HA effectiveness in rolling-restart or crash scenarios.
src/stdlib/secrets.rs Refactors the provider layer to support larri-socket alongside env: introduces SocketProviderConfig parsing, symlink-safe path validation, zeroizing ProviderLookup, and the new ProviderEndpointLabel diagnostic type. SecretValue::new gated to #[cfg(test)] forces production callers through new_zeroizing. Logic is clean with no issues found.
src/secret.rs Splits construction into new (test-only) and new_zeroizing (production) so provider-owned Zeroizing<String> values flow into SecretValue without an intermediate plaintext copy. Minimal, correct change.
tests/language_features_tests.rs Adds end-to-end integration tests for the larri-socket provider: successful lookup with redaction verification, and a missing-scope test that verifies the socket is never contacted when configuration is incomplete.
Cargo.toml Adds serde feature to zeroize so Zeroizing<String> can be used as a Deserialize target in the socket response enum. No version bump; minimal change.
docs/RUNTIME_REFERENCE.md Documents the four new environment variables with values, defaults, and examples that match the implementation constraints.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant ntnt as ntnt runtime
    participant PG as ProviderGroup
    participant SP as SocketSecretProvider
    participant FS as Filesystem
    participant Agent as Larri host agent

    ntnt->>PG: lookup("SECRET_NAME")
    loop per endpoint, up to 2 attempts
        PG->>SP: lookup("SECRET_NAME")
        SP->>FS: symlink_metadata(path)
        alt symlink or non-socket
            FS-->>SP: InvalidConfiguration (terminal)
        else not found
            FS-->>SP: Unavailable (retriable)
        else ok
            FS-->>SP: proceed
        end
        SP->>Agent: connect (timeout-bounded)
        SP->>Agent: write JSON request + newline
        SP->>Agent: shutdown(Write)
        Agent-->>SP: newline-framed JSON response + EOF
        Note over SP: validate protocol, request_id, scope
        alt "status=found, scope ok, size ok"
            SP-->>PG: ProviderLookup::Found(Zeroizing)
        else "status=missing"
            SP-->>PG: ProviderLookup::Missing
        else "status=unavailable"
            SP-->>PG: Unavailable (retry/failover)
        else "status=access_denied / invalid_request / invalid_configuration"
            SP-->>PG: terminal error
        else malformed frame / scope mismatch / ID mismatch
            SP-->>PG: terminal error
        end
    end
    PG-->>ntnt: SecretValue or error
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant ntnt as ntnt runtime
    participant PG as ProviderGroup
    participant SP as SocketSecretProvider
    participant FS as Filesystem
    participant Agent as Larri host agent

    ntnt->>PG: lookup("SECRET_NAME")
    loop per endpoint, up to 2 attempts
        PG->>SP: lookup("SECRET_NAME")
        SP->>FS: symlink_metadata(path)
        alt symlink or non-socket
            FS-->>SP: InvalidConfiguration (terminal)
        else not found
            FS-->>SP: Unavailable (retriable)
        else ok
            FS-->>SP: proceed
        end
        SP->>Agent: connect (timeout-bounded)
        SP->>Agent: write JSON request + newline
        SP->>Agent: shutdown(Write)
        Agent-->>SP: newline-framed JSON response + EOF
        Note over SP: validate protocol, request_id, scope
        alt "status=found, scope ok, size ok"
            SP-->>PG: ProviderLookup::Found(Zeroizing)
        else "status=missing"
            SP-->>PG: ProviderLookup::Missing
        else "status=unavailable"
            SP-->>PG: Unavailable (retry/failover)
        else "status=access_denied / invalid_request / invalid_configuration"
            SP-->>PG: terminal error
        else malformed frame / scope mismatch / ID mismatch
            SP-->>PG: terminal error
        end
    end
    PG-->>ntnt: SecretValue or error
Loading

Reviews (2): Last reviewed commit: "feat: add Larri socket secrets provider" | Re-trigger Greptile

Comment thread src/stdlib/secrets/socket.rs Outdated
Comment thread src/stdlib/secrets/socket.rs Outdated
@larimonious

Copy link
Copy Markdown
Contributor Author

Superseded by #165. The replacement is rebuilt from main as a backend-neutral unix-socket provider, with a generic protocol, /run/ntnt-secrets trust root, and no Larri-specific language surface.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant