mirror of
https://github.com/Hmbown/DeepSeek-TUI.git
synced 2026-09-03 06:50:13 +08:00
fix(ci): close the CodeQL cache-poisoning Highs in the release workflows
All 19 open actions/cache-poisoning/poisonable-step alerts (#88-#106) sit in release.yml, release-candidate.yml and release-artifacts.yml: jobs interpolated the caller SHA into ref/caches and let setup-node's implicit npm cache key on run identity. Now caller source_sha is pinned against github.sha by a pin job (refusing any retarget), checkout and build identity use GITHUB_SHA through env indirection instead of template interpolation, rust-cache keys use stable prefix-key values, and setup-node's implicit package cache is off where it cannot be keyed safely. Also carries the CodeQL #107 fix from the same lane: catalog_models_dev.py prints remote limit values as numbers/null/redacted only and drops query and fragment material from source URLs before logging. Harvested from #5401 (workflow+script files; CHANGELOG edit intentionally excluded here and lands with the release branch; GHSA advisory text split to a later PR as advised). No-Issue: CodeQL alert remediation (alerts #88-#107); no single user-facing issue tracks these Signed-off-by: Hunter Bown <hunter@hmbown.com>
This commit is contained in:
47
.github/scripts/release-workflows.test.js
vendored
47
.github/scripts/release-workflows.test.js
vendored
@@ -120,7 +120,13 @@ assert.doesNotMatch(candidate, /^ (push|pull_request|schedule):/m);
|
||||
assert.match(candidate, /uses: \.\/\.github\/workflows\/release-artifacts\.yml/);
|
||||
assert.match(candidate, /source_sha: \$\{\{ needs\.resolve\.outputs\.sha \}\}/);
|
||||
assert.match(candidate, /^ web:\n/m);
|
||||
assert.match(candidate, /ref: \$\{\{ needs\.resolve\.outputs\.sha \}\}/);
|
||||
assert.doesNotMatch(
|
||||
candidate,
|
||||
/ref: \$\{\{ needs\.resolve\.outputs\.sha \}\}/,
|
||||
"candidate jobs must checkout GITHUB_SHA, not interpolate the dispatch SHA into ref",
|
||||
);
|
||||
assert.match(candidate, /cache-dependency-path: web\/package-lock\.json/);
|
||||
assert.match(candidate, /package-manager-cache: false/);
|
||||
assert.match(candidate, /working-directory: web/);
|
||||
for (const command of [
|
||||
"npm ci",
|
||||
@@ -259,14 +265,51 @@ assert.match(artifacts, /codew-windows-arm64\.exe/);
|
||||
assert.match(artifacts, /CodeWhaleSetup\.exe/);
|
||||
assert.match(artifacts, /assemble-release-assets\.js --verify release-assets/);
|
||||
assert.match(artifacts, /CODEWHALE_SMOKE_ASSETS_DIR/);
|
||||
assert.match(artifacts, /^ pin:\n/m);
|
||||
assert.match(artifacts, /Require source_sha equals github\.sha/);
|
||||
assert.doesNotMatch(
|
||||
artifacts,
|
||||
/ref: \$\{\{ inputs\.source_sha \}\}/,
|
||||
"artifact jobs must checkout GITHUB_SHA, not interpolate the caller SHA into ref",
|
||||
);
|
||||
assert.match(artifacts, /prefix-key: v1-\$\{\{ runner\.os \}\}-\$\{\{ runner\.arch \}\}-stable/);
|
||||
assert.equal(
|
||||
(artifacts.match(/package-manager-cache: false/g) || []).length,
|
||||
2,
|
||||
"assemble and smoke must disable setup-node's implicit npm cache",
|
||||
);
|
||||
const bundleStep = namedStep(artifacts, "Create and checksum platform archives");
|
||||
assert.match(bundleStep, /git show -s --format=%ct "\$\{\{ inputs\.source_sha \}\}"/);
|
||||
assert.match(bundleStep, /SOURCE_SHA: \$\{\{ github\.sha \}\}/);
|
||||
assert.match(bundleStep, /git show -s --format=%ct "\$\{SOURCE_SHA\}"/);
|
||||
assert.match(
|
||||
bundleStep,
|
||||
/SOURCE_DATE_EPOCH="\$\{source_date_epoch\}"[\s\\]+bash scripts\/release\/create-release-bundles\.sh artifacts bundles/,
|
||||
);
|
||||
assert.doesNotMatch(bundleStep, /inputs\.source_sha/);
|
||||
assert.doesNotMatch(bundleStep, /\bdate\b/, "bundle timestamps must come from the pinned source commit, not wall-clock time");
|
||||
|
||||
const rustCacheBlocks = [...artifacts.matchAll(/uses: Swatinem\/rust-cache@[\s\S]*?(?=\n - )/g)].map(
|
||||
(match) => match[0],
|
||||
);
|
||||
assert.ok(rustCacheBlocks.length >= 1, "shared artifact workflow must pin rust-cache");
|
||||
for (const block of rustCacheBlocks) {
|
||||
assert.doesNotMatch(block, /github\.(event|ref|sha)|inputs\./);
|
||||
}
|
||||
|
||||
const parity = release.match(/\n parity:\n([\s\S]*?)\n artifacts:\n/);
|
||||
assert.ok(parity, "public release must retain a parity job");
|
||||
assert.doesNotMatch(
|
||||
parity[1],
|
||||
/ref: \$\{\{ needs\.resolve\.outputs\.sha \}\}/,
|
||||
"parity must checkout GITHUB_SHA after resolve, not interpolate the tag SHA into ref",
|
||||
);
|
||||
assert.match(parity[1], /prefix-key: v1-\$\{\{ runner\.os \}\}-\$\{\{ runner\.arch \}\}-stable/);
|
||||
const parityRustCache = [...parity[1].matchAll(/uses: Swatinem\/rust-cache@[\s\S]*?(?=\n - )/g)].map(
|
||||
(match) => match[0],
|
||||
);
|
||||
assert.equal(parityRustCache.length, 1, "parity must pin exactly one rust-cache");
|
||||
assert.doesNotMatch(parityRustCache[0], /github\.(event|ref|sha)|inputs\./);
|
||||
|
||||
assert.equal(allReleaseAssetNames().length, 34);
|
||||
assert.match(release, /^ artifacts:\n/m);
|
||||
assert.match(release, /uses: \.\/\.github\/workflows\/release-artifacts\.yml/);
|
||||
|
||||
69
.github/workflows/release-artifacts.yml
vendored
69
.github/workflows/release-artifacts.yml
vendored
@@ -24,9 +24,31 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: -Dwarnings
|
||||
DEEPSEEK_BUILD_SHA: ${{ inputs.source_sha }}
|
||||
# Build identity is the trusted workflow SHA. Callers pass source_sha only
|
||||
# so `pin` can refuse a mismatch; it must not retarget checkout or caches.
|
||||
DEEPSEEK_BUILD_SHA: ${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
pin:
|
||||
name: Pin caller SHA to this run
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Require source_sha equals github.sha
|
||||
env:
|
||||
SOURCE_SHA: ${{ inputs.source_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${#SOURCE_SHA}" -ne 40 || "${SOURCE_SHA}" =~ [^0-9a-fA-F] ]]; then
|
||||
echo "::error::source_sha must be a full 40-character commit SHA." >&2
|
||||
exit 1
|
||||
fi
|
||||
expected="$(printf '%s' "${SOURCE_SHA}" | tr '[:upper:]' '[:lower:]')"
|
||||
actual="$(printf '%s' "${GITHUB_SHA}" | tr '[:upper:]' '[:lower:]')"
|
||||
if [[ "${actual}" != "${expected}" ]]; then
|
||||
echo "::error::Reusable workflow SHA ${actual} does not match source_sha ${SOURCE_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.platform }}
|
||||
# FreeBSD is a source-build target validated via `cargo check --target x86_64-unknown-freebsd -p codewhale-cli --locked`
|
||||
@@ -93,17 +115,34 @@ jobs:
|
||||
shim_artifact: codew-windows-arm64.exe
|
||||
tui_artifact: codewhale-tui-windows-arm64.exe
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: pin
|
||||
steps:
|
||||
# No ref: — GITHUB_SHA only. CodeQL treats workflow_call checkout-with-ref
|
||||
# and any ref named *sha* as an untrusted checkout (cache-poisoning).
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ inputs.source_sha }}
|
||||
- uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # master 2026-07-18
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: ${{ matrix.target }}
|
||||
# Privileged artifact builds check out inputs.source_sha. Do not
|
||||
# restore or save rust-cache / sccache here — those keys are shared
|
||||
# with default-branch CI (CodeQL #95–#96, #104–#106).
|
||||
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
id: sccache
|
||||
continue-on-error: true
|
||||
- name: Enable sccache
|
||||
if: steps.sccache.outcome == 'success'
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "SCCACHE_GHA_ENABLED=true"
|
||||
echo "RUSTC_WRAPPER=sccache"
|
||||
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1"
|
||||
} >> "${GITHUB_ENV}"
|
||||
# Restore after the trusted lockfile is on disk. Key is OS + arch +
|
||||
# explicit stable toolchain + rust-cache's Cargo.lock / rust-toolchain
|
||||
# hash. Never interpolate github.event, github.ref, github.sha, or inputs.
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
with:
|
||||
cache-bin: false
|
||||
prefix-key: v1-${{ runner.os }}-${{ runner.arch }}-stable
|
||||
- name: Build static Linux binaries (musl)
|
||||
if: endsWith(matrix.target, '-unknown-linux-musl')
|
||||
shell: bash
|
||||
@@ -244,19 +283,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ inputs.source_sha }}
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
path: artifacts
|
||||
pattern: '*'
|
||||
- name: Create and checksum platform archives
|
||||
shell: bash
|
||||
env:
|
||||
SOURCE_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
source_date_epoch="$(git show -s --format=%ct "${{ inputs.source_sha }}")"
|
||||
source_date_epoch="$(git show -s --format=%ct "${SOURCE_SHA}")"
|
||||
if [[ ! "${source_date_epoch}" =~ ^[0-9]+$ ]]; then
|
||||
echo "Could not read a Unix timestamp for source commit ${{ inputs.source_sha }}" >&2
|
||||
echo "Could not read a Unix timestamp for source commit ${SOURCE_SHA}" >&2
|
||||
exit 1
|
||||
fi
|
||||
SOURCE_DATE_EPOCH="${source_date_epoch}" \
|
||||
@@ -278,8 +317,6 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ inputs.source_sha }}
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
path: artifacts
|
||||
@@ -320,11 +357,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ inputs.source_sha }}
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20
|
||||
package-manager-cache: false
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
path: intermediate-artifacts
|
||||
@@ -346,11 +382,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ inputs.source_sha }}
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20
|
||||
package-manager-cache: false
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: codewhale-release-assets
|
||||
@@ -371,7 +406,7 @@ jobs:
|
||||
{
|
||||
echo "### Release artifact candidate"
|
||||
echo ""
|
||||
echo "- Source: \`${{ inputs.source_sha }}\`"
|
||||
echo "- Source: \`${{ github.sha }}\`"
|
||||
echo "- Version metadata: \`${{ inputs.version }}\`"
|
||||
echo "- Inventory: 7 targets / 34 files (single binary; 7 legacy alias assets)"
|
||||
echo "- Publication: none (Actions artifact \`codewhale-release-assets\` only)"
|
||||
|
||||
10
.github/workflows/release-candidate.yml
vendored
10
.github/workflows/release-candidate.yml
vendored
@@ -31,6 +31,7 @@ jobs:
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20
|
||||
package-manager-cache: false
|
||||
- uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # stable 2026-07-18
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -82,15 +83,14 @@ jobs:
|
||||
run:
|
||||
working-directory: web
|
||||
steps:
|
||||
# resolve already proved expected_sha equals GITHUB_SHA. Do not
|
||||
# interpolate that SHA into checkout or the npm cache key.
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 22
|
||||
# No npm cache: this job checkouts needs.resolve.outputs.sha from
|
||||
# workflow_dispatch (CodeQL #88–#94). Lockfile-keyed caches would
|
||||
# still be writable from that checkout into the default branch.
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
- name: Install web dependencies
|
||||
run: npm ci
|
||||
- name: Check public facts drift
|
||||
|
||||
30
.github/workflows/release.yml
vendored
30
.github/workflows/release.yml
vendored
@@ -116,17 +116,27 @@ jobs:
|
||||
needs: resolve
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# resolve already proved GITHUB_SHA equals the tag commit. Do not
|
||||
# interpolate needs.resolve.outputs.sha into checkout or cache keys —
|
||||
# CodeQL treats a *sha* ref as an untrusted checkout on workflow_dispatch
|
||||
# (default-branch cache write).
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
- uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # master 2026-07-18
|
||||
with:
|
||||
toolchain: stable
|
||||
components: clippy, rustfmt
|
||||
# Privileged release jobs check out a resolved SHA (tag or
|
||||
# workflow_dispatch). Do not restore or save GitHub Actions caches
|
||||
# here: a shared rust-cache / sccache key would let that checkout
|
||||
# write into the default-branch cache (CodeQL #88–#103).
|
||||
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
id: sccache
|
||||
continue-on-error: true
|
||||
- name: Enable sccache
|
||||
if: steps.sccache.outcome == 'success'
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "SCCACHE_GHA_ENABLED=true"
|
||||
echo "RUSTC_WRAPPER=sccache"
|
||||
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1"
|
||||
} >> "${GITHUB_ENV}"
|
||||
- name: Install Linux system dependencies
|
||||
run: |
|
||||
for i in 1 2 3 4 5; do
|
||||
@@ -135,6 +145,13 @@ jobs:
|
||||
sleep 15
|
||||
done
|
||||
sudo apt-get install -y libdbus-1-dev pkg-config
|
||||
# Restore after the trusted lockfile is on disk. Key is OS + arch +
|
||||
# explicit stable toolchain + rust-cache's Cargo.lock / rust-toolchain
|
||||
# hash. Never interpolate github.event, github.ref, github.sha, or inputs.
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
with:
|
||||
cache-bin: false
|
||||
prefix-key: v1-${{ runner.os }}-${{ runner.arch }}-stable
|
||||
- name: Format check
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Compile check
|
||||
@@ -416,6 +433,7 @@ jobs:
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20
|
||||
package-manager-cache: false
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: codewhale-release-assets
|
||||
|
||||
@@ -46,22 +46,6 @@ def die(msg: str, code: int = 1) -> None:
|
||||
raise SystemExit(code)
|
||||
|
||||
|
||||
def _safe_catalog_path(path: str) -> str:
|
||||
"""Catalog id path only — never a URL, token, or raw JSON value."""
|
||||
return "".join(ch if ch.isalnum() or ch in ".-_/" else "?" for ch in path)[:200]
|
||||
|
||||
|
||||
def _safe_source_label(source: str) -> str:
|
||||
"""Log scheme + host (or file name), never query strings or credentials."""
|
||||
if source.startswith("file:"):
|
||||
return "file:<local>"
|
||||
if source.startswith("url:"):
|
||||
rest = source[4:]
|
||||
host = rest.split("://", 1)[-1].split("/", 1)[0].split("@")[-1]
|
||||
return f"url:https://{host}/" if host else "url:<redacted>"
|
||||
return "source:<redacted>"
|
||||
|
||||
|
||||
def load_json_bytes(raw: bytes, source: str) -> Any:
|
||||
try:
|
||||
text = raw.decode("utf-8")
|
||||
@@ -186,6 +170,34 @@ def public_models_dev_document(data: dict[str, Any]) -> dict[str, Any]:
|
||||
return out
|
||||
|
||||
|
||||
def public_source_label(source: str) -> str:
|
||||
"""Log a catalog origin without query/fragment (tokens live there)."""
|
||||
if source.startswith("url:"):
|
||||
url = source[4:]
|
||||
for sep in ("?", "#"):
|
||||
url = url.split(sep, 1)[0]
|
||||
return f"url:{url}"
|
||||
return source
|
||||
|
||||
|
||||
def public_limit_value(value: Any) -> str:
|
||||
"""Format a catalog limit for logs. Never print credential-shaped strings.
|
||||
|
||||
Remote catalog JSON is tainted for clear-text-logging rules. Only numeric
|
||||
limits are meaningful here; anything else (including token-shaped strings)
|
||||
is replaced with a constant so the raw value cannot reach stdout.
|
||||
"""
|
||||
if isinstance(value, bool):
|
||||
return "redacted"
|
||||
if value is None:
|
||||
return "null"
|
||||
if isinstance(value, int):
|
||||
return str(value)
|
||||
if isinstance(value, float):
|
||||
return format(value, ".6g")
|
||||
return "redacted"
|
||||
|
||||
|
||||
def catalog_stats(data: dict[str, Any]) -> str:
|
||||
models = data.get("models") or {}
|
||||
providers = data.get("providers") or {}
|
||||
@@ -338,9 +350,11 @@ def _collect_limit_drift(
|
||||
bundled = seed_limit.get(field)
|
||||
upstream = upstream_limit.get(field)
|
||||
if bundled != upstream:
|
||||
# Log only the catalog path and field name. Never print raw
|
||||
# upstream/bundled values — CodeQL #107 (clear-text logging).
|
||||
drift.append(f"{_safe_catalog_path(path)}: limit.{field} differs")
|
||||
drift.append(
|
||||
f"{path}: limit.{field} "
|
||||
f"bundled={public_limit_value(bundled)} "
|
||||
f"upstream={public_limit_value(upstream)}"
|
||||
)
|
||||
|
||||
|
||||
def cmd_drift(args: argparse.Namespace) -> None:
|
||||
@@ -406,12 +420,12 @@ def cmd_drift(args: argparse.Namespace) -> None:
|
||||
drift,
|
||||
)
|
||||
|
||||
print(f"bundled seed: {seed_path.name}")
|
||||
print(f"upstream: {_safe_source_label(source)}")
|
||||
print(f"bundled seed: {seed_path}")
|
||||
print(f"upstream: {public_source_label(source)}")
|
||||
if missing_upstream:
|
||||
print("removed upstream (bundled id no longer present):")
|
||||
for path in missing_upstream:
|
||||
print(f" - {_safe_catalog_path(path)}")
|
||||
print(f" - {path}")
|
||||
if drift:
|
||||
print(f"limit drift detected ({len(drift)}):")
|
||||
for path in drift:
|
||||
|
||||
@@ -99,6 +99,75 @@ class CatalogModelsDevScriptTests(unittest.TestCase):
|
||||
self.assertIn("disk writes are intentionally unsupported", proc.stderr)
|
||||
self.assertFalse(target.exists(), "refresh must remain dry-run only")
|
||||
|
||||
def test_public_limit_value_never_echoes_tokens(self) -> None:
|
||||
sys.path.insert(0, str(ROOT / "scripts"))
|
||||
import catalog_models_dev as mod # type: ignore
|
||||
|
||||
self.assertEqual(mod.public_limit_value(128000), "128000")
|
||||
self.assertEqual(mod.public_limit_value(None), "null")
|
||||
self.assertEqual(mod.public_limit_value("sk-this-is-a-token"), "redacted")
|
||||
self.assertEqual(mod.public_limit_value({"authorization": "Bearer secret"}), "redacted")
|
||||
self.assertEqual(mod.public_limit_value(True), "redacted")
|
||||
|
||||
def test_public_source_label_strips_query_string(self) -> None:
|
||||
sys.path.insert(0, str(ROOT / "scripts"))
|
||||
import catalog_models_dev as mod # type: ignore
|
||||
|
||||
self.assertEqual(
|
||||
mod.public_source_label("url:https://models.dev/catalog.json?token=sk-leak"),
|
||||
"url:https://models.dev/catalog.json",
|
||||
)
|
||||
self.assertEqual(mod.public_source_label("file:/tmp/catalog.json"), "file:/tmp/catalog.json")
|
||||
|
||||
def test_drift_does_not_print_token_shaped_upstream_limits(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
seed = Path(td) / "seed.json"
|
||||
upstream = Path(td) / "upstream.json"
|
||||
seed.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"models": {
|
||||
"demo": {"limit": {"context": 1000, "output": 100}},
|
||||
},
|
||||
"providers": {},
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
upstream.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"models": {
|
||||
"demo": {
|
||||
"limit": {
|
||||
"context": 1000,
|
||||
"output": "sk-this-is-a-token",
|
||||
}
|
||||
},
|
||||
},
|
||||
"providers": {},
|
||||
"token": "sk-header-token",
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
env = os.environ.copy()
|
||||
env["CODEWHALE_MODELS_DEV_PATH"] = str(upstream)
|
||||
proc = subprocess.run(
|
||||
[sys.executable, str(SCRIPT), "drift", "--seed", str(seed)],
|
||||
cwd=ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
env=env,
|
||||
)
|
||||
combined = f"{proc.stdout}\n{proc.stderr}"
|
||||
self.assertNotEqual(proc.returncode, 0)
|
||||
self.assertNotIn("sk-this-is-a-token", combined)
|
||||
self.assertNotIn("sk-header-token", combined)
|
||||
self.assertIn("redacted", combined)
|
||||
self.assertIn("limit.output", combined)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user