Files
windmill/cli/test/app_policy_bundle_unit.test.ts
Ruben Fiszel 343ce6e143 fix: derive a raw app's policy on deploy, and default an omitted execution_mode (#10733)
* fix: default an omitted app policy execution_mode to publisher

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: drop stale comments claiming execution_mode is required

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: derive a raw app's policy on deploy instead of trusting the caller's

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore: pin the ee ref to the companion branch

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: vendor the raw-app policy derivation into the bundle job

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: note the vendored raw-app policy bundle

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: derive the policy on a value-only raw-source update too

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: reject raw-app runnables whose shape yields an unusable grant

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: cache the new policy query and tighten raw-app runnable validation

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: let the policy bundle drift guard survive a CRLF checkout

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to 23431f5cf1d627051ded89111bbf2e301e9db456

This commit updates the EE repository reference after PR #729 was merged in windmill-ee-private.

Previous ee-repo-ref: 0bdf8818fa115ad6b0d14f3117a18e8a580cce4d

New ee-repo-ref: 23431f5cf1d627051ded89111bbf2e301e9db456

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-08-18 01:52:10 +02:00

70 lines
2.8 KiB
TypeScript

/**
* The raw-app bundle job carries the frontend's policy derivation, vendored by
* cli/generate-app-policy.ts into backend/windmill-api/src/apps_raw_policy.gen.js
* and prepended to the job script.
*
* If that copy drifts from the frontend source, deployed apps get policy keys
* the app editor would not have written, and every runnable is refused at run
* time with "forbidden by policy" — an app that deploys and then does nothing.
* So rebuild the bundle here and fail when the committed one no longer matches.
* Fix by running `bun run gen:app-policy` from cli/.
*
* No backend required.
*/
import { expect, test, describe } from "bun:test";
import { readFileSync } from "node:fs";
import { buildAppPolicyBundle, OUT_FILE } from "../generate-app-policy.ts";
describe("raw app policy bundle", () => {
test("the committed bundle matches the frontend source", async () => {
// Line endings normalized: a CRLF checkout is the same bundle, and must not
// read as drift (the committed file's header arrives as CRLF on Windows).
const lf = (s: string) => s.replace(/\r\n/g, "\n");
expect(lf(readFileSync(OUT_FILE, "utf-8"))).toBe(
lf(await buildAppPolicyBundle()),
);
});
test("derives the keys the app editor writes", async () => {
// Exercise the committed artifact itself, not the frontend module: it is
// what actually runs on the worker.
// A module's top-level `var` is not a global, and the job prepends this
// bundle into its own module, so reach the binding the same way it does.
const { updateRawAppPolicy } = new Function(
`${readFileSync(OUT_FILE, "utf-8")}\nreturn __wmillAppPolicy`,
)();
const content = "export async function main(a: string) { return a }\n";
const sha = new Bun.CryptoHasher("sha256").update(content).digest("hex");
const policy = await updateRawAppPolicy(
{
inline: {
type: "inline",
inlineScript: { content, language: "bun" },
fields: {
pinned: { type: "static", value: "by-the-publisher" },
secret: { type: "static", value: "shh", sensitive: true },
},
},
by_flow: { type: "path", runType: "flow", path: "u/admin/f", fields: {} },
},
undefined,
);
expect(Object.keys(policy.triggerables_v2).sort()).toEqual([
"by_flow:flow/u/admin/f",
`inline:rawscript/${sha}`,
]);
// `sensitive_inputs` is what makes the server encrypt the arg before it
// reaches the job, so losing it would silently store the value in plaintext.
const inline = policy.triggerables_v2[`inline:rawscript/${sha}`];
expect(inline.static_inputs).toEqual({
pinned: "by-the-publisher",
secret: "shh",
});
expect(inline.sensitive_inputs).toEqual(["secret"]);
});
});