Skip to content

sdk: make render() previews match the data plane's single substitution - #1467

Open
dwin-gharibi wants to merge 1 commit into
TencentCloud:masterfrom
dwin-gharibi:sdk-render-inject-divergence
Open

sdk: make render() previews match the data plane's single substitution#1467
dwin-gharibi wants to merge 1 commit into
TencentCloud:masterfrom
dwin-gharibi:sdk-render-inject-divergence

Conversation

@dwin-gharibi

Copy link
Copy Markdown
Contributor

Closes #1466.

Motivation

Inject.render() exists to show the operator the header value that will actually be sent. The Go and
Python implementations substituted every ${SECRET} occurrence, but CubeEgress substitutes only the
first (access_phase.lua:171 passes 1 as gsub's count limit). For a format like
Basic ${SECRET}:${SECRET} the preview said Basic tok:tok while the upstream received
Basic tok:${SECRET}.

What this changes

Aligns the two outliers with the server:

  • sdk/go/policy.gostrings.ReplaceAll(...)strings.Replace(..., 1)
  • sdk/python/cubesandbox/_policy.pyfmt.replace(...)fmt.replace(..., 1)

The Node SDK already matches (JS String.replace with a string pattern replaces only the first), and the
server is unchanged.

No comment changes.

Why change the SDKs rather than the server

Two of the four implementations already do single substitution, and the server's explicit 1 limit plus
its "format has no ${SECRET} placeholder" warning read as a deliberate single-substitution contract.
More importantly, changing the server would alter the header value emitted for any existing policy that
repeats the placeholder — a live data-plane change. Changing a preview helper cannot break traffic.

If the maintainers would rather the contract be "replace all", the change is dropping the 1 in
access_phase.lua:171 and reverting these two lines; the tests here document the contract either way so
the decision is visible.

Testing

New:

  • sdk/go/policy_render_test.goTestInjectRenderMatchesServerSubstitution, table-driven over the six
    cases from the issue including the default (empty) format, a %-containing secret, and a format with
    no placeholder.
  • sdk/python/tests/test_policy_render.py — the same table.

Red/green, per SDK:

Go, with policy.go reverted:
  --- FAIL: TestInjectRenderMatchesServerSubstitution
      policy_render_test.go:27: Render() = "Basic tok:tok", want "Basic tok:${SECRET}"
      policy_render_test.go:27: Render() = "tok-tok-tok", want "tok-${SECRET}-${SECRET}"
Go, with the fix:
  ok  github.com/tencentcloud/CubeSandbox/sdk/go  0.820s

Python, with _policy.py reverted:
  FAILED tests/test_policy_render.py::test_render_matches_server_substitution
Python, with the fix:
  1 passed in 0.03s

CI gates checked locally:

  • gofmt -l . in sdk/go — clean (fmt-check).
  • go build ./... in sdk/go — clean.
  • sdk-test-check covers the SDK suites; both new tests sit in the existing test locations.

Risk / rollout

None to the data plane. The only behaviour change is that render() now returns the same string the
server produces. Anyone who was relying on render() to expand a repeated placeholder was getting a
value the server never emitted.

Signed-off-by: Dwin Gharibi <dwin.gharibi@email.kntu.ac.ir>
@dwin-gharibi
dwin-gharibi requested a review from tinklone as a code owner August 21, 2026 11:31
Copilot AI lite review requested due to automatic review settings August 21, 2026 11:31
@dwin-gharibi
dwin-gharibi requested a review from wbzdssm as a code owner August 21, 2026 11:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@@ -0,0 +1,16 @@
from cubesandbox._policy import Inject

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor nit: every other file under sdk/python/tests/ (test_policy.py, test_sandbox.py, conftest.py) carries the Apache-2.0 SPDX header. Add it here for consistency:

# Copyright (c) 2026 Tencent Inc.
# SPDX-License-Identifier: Apache-2.0

@cubesandboxbot

Copy link
Copy Markdown

Review: sdk: make render() previews match the data plane's single substitution (#1467)

Verdict: Approve. The change is correct, well-scoped, and thoroughly tested. It aligns the two divergent SDK preview helpers with the server's actual substitution behavior. This review is AI-generated.

What I verified against the base tree

  • Server contractCubeEgress/lua/access_phase.lua:184: string.gsub(fmt, "%${SECRET}", escaped, 1) replaces only the first occurrence, and pre-escapes %%% in the secret so the emitted header value is the literal secret (the %${SECRET} pattern has no captures). The n == 0 branch warns and treats the format as a literal.
  • Node SDKsdk/node/src/policy.ts:98: fmt.replace("${SECRET}", inject.secret). JS String.replace with a string pattern replaces only the first occurrence, so Node already conformed; no change was needed. Confirmed.
  • Go SDK (changed): strings.ReplaceAllstrings.Replace(..., 1). Correct; output matches the server for every case, including a literal % in the secret (Go inserts the secret verbatim, which equals the server's escaped-then-literal result).
  • Python SDK (changed): fmt.replace("${SECRET}", self.secret, 1). Correct, same reasoning.
  • Edge cases checked: default/empty format, whitespace-only format, %-containing secrets, secrets that themselves contain ${SECRET}, and formats with no placeholder all produce identical output between server and SDKs.
  • No existing tests break: TestInjectRender (sdk/go/aligned_test.go:241) and test_inject_render (sdk/python/tests/test_sandbox.py:495) use single-placeholder formats and remain green.
  • Completeness: Only Go, Python, Node, and the Lua server render inject headers — verified no other SDKs (Java/Rust/etc.) in the tree. All four now agree.

Test quality

The table-driven Go test and parametrized Python test cover the six meaningful cases from the issue (default format, repeated/three placeholders, % in secret, no placeholder) and sit in the existing test locations with matching conventions.

Non-blocking nit

  • sdk/python/tests/test_policy_render.py is the only file under sdk/python/tests/ missing the # Copyright (c) 2026 Tencent Inc. / # SPDX-License-Identifier: Apache-2.0 header that every sibling file carries (see test_policy.py, test_sandbox.py, conftest.py). Inline comment posted.

Design note (non-blocking)

This is a deliberate, documented behavior change to a public SDK preview helper (single vs. all substitution). The rationale is sound: two of the four implementations already did single substitution, and the server's explicit 1 limit plus its no-placeholder WARN read as an intentional single-substitution contract. Changing the data plane to replace-all instead would have altered the emitted header value for any existing policy that repeats the placeholder — a live data-plane change — so fixing the preview helpers is the right call. The PR's offer to flip the contract (drop the 1) if maintainers disagree is well framed, and the new tests document the contract either way.

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.

[Bug Report] SDK render() previews disagree with the data plane when the inject format repeats ${SECRET}

2 participants