fix(kuna)!: separate verification policy from analysis identity (#8699)

* feat(kuna): verify release 1.989

* fix(kuna)!: separate verification policy from analysis identity

* docs(kuna): show historical evidence recovery

* test(kuna): cover verification warnings through MCP
This commit is contained in:
morluto authored and GitHub committed 2026-11-03 01:34:40 +08:00
1 parent 3c05b4d3fe
commit 75c3cab540
10 files changed
+106 -19

No files matched your search

+1 -1
View File
@@ -60,7 +60,7 @@ pin inspection sources; they are not included in the npm package.
| ghidra-nativeaot | [MIT](https://github.com/Washi1337/ghidra-nativeaot/blob/effeb734fc570c32650f88b159608979dc7b423e/LICENSE.md) | Pinned inspection source; derived parser attribution above. |
| JADX | [Apache-2.0](https://github.com/skylot/jadx/blob/v1.5.6/LICENSE) | The audited jadx-headless-mcp source uses JADX 1.5.6 for Android decompilation; the engine JAR is separately supplied. |
| jadx-headless-mcp | [Apache-2.0](https://github.com/1013503897/jadx-headless-mcp/blob/5844800a486d2248fa949b74c9896285f5b54de2/LICENSE) | v0.7.1; pinned submodule and user-supplied JAR. |
| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.988/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.988/NOTICE) | Bring your own `kuna`; REA verified 1.988 and never downloads, installs or bundles it. REA invokes it as a separate process. |
| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.989/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.989/NOTICE) | Bring your own `kuna`; REA verified 1.989 and never downloads, installs or bundles it. REA invokes it as a separate process. |
| Apktool | [Apache-2.0](https://github.com/iBotPeaches/Apktool/blob/master/LICENSE.md) | User-supplied Android resource decoder. |
| Binwalk | [MIT](https://github.com/ReFirmLabs/binwalk/blob/4fdab3d464d97b68e0af9088df3f9e2e1545b21c/LICENSE) | v3.1.0; pinned submodule and user-supplied firmware engine. |
| Unblob | [MIT](https://github.com/onekey-sec/unblob/blob/1fcc7a0a584a70a96c31f5a276c20944d199a089/LICENSE) | 26.6.4; pinned submodule and user-supplied firmware engine. |
+19 -1
View File
@@ -1,9 +1,27 @@
# Kuna provider
[Kuna](https://github.com/Noelo-Lab/kuna) is an Apache-2.0 decompiler distributed as one `kuna` executable. For a new installation, use Kuna 1.988, REA's recommended verified release. REA uses a `kuna` you supply; it does not download, install, update or bundle Kuna. REA accepts other releases, which it reports as unverified in `rea doctor` and in each analysis profile. The verified-release flag compares the reported version; it does not authenticate the executable. The 1.988 real-provider checks ran on macOS arm64; Linux and other hosts have not been verified with that release.
[Kuna](https://github.com/Noelo-Lab/kuna) is an Apache-2.0 decompiler distributed as one `kuna` executable. For a new installation, use Kuna 1.989, REA's recommended verified release. REA uses a `kuna` you supply; it does not download, install, update or bundle Kuna. REA accepts other releases; `rea doctor` and analysis Evidence report them as unverified. Verification compares the reported version; it does not authenticate the executable. Analysis profiles retain the observed version and executable digest, independently of REA's current verified-release policy. The 1.989 real-provider checks ran on macOS arm64; Linux and other hosts have not been verified with that release.
Kuna is static: it reads the selected image and never executes it.
This profile change makes earlier Kuna analysis snapshots and resumable directory
exports incompatible, even with the same Kuna executable. Keep the old files:
their recorded Evidence remains historical. To continue analysis, open the
binary without the old snapshot and save a new one, or choose a new export
directory. Existing snapshot Evidence can be extracted from its
`evidence_bundle` JSON member and imported as a separate Evidence bundle; that
does not restore cached workflow entries or resume the old directory export.
For example, using `jq` and a new output path:
```bash
jq -e '.evidence_bundle | select(type == "object")' old-snapshot.json > history.json
rea evidence-import history.json --json
```
The CLI command validates the bundle in a one-shot session. To use the records
in an MCP session, call `import_evidence_bundle` with the extracted file path.
This validates the embedded bundle, not the rest of the snapshot container.
## Configure
```bash
+5
View File
@@ -451,6 +451,11 @@ Mach-O fixture and caller-built symbol-bearing and stripped x86-64 ELF fixtures.
Linux and other hosts have not been verified with 1.988; this is not a repeat
of the larger 1.833 matrix.
Kuna 1.989 passed the real-provider lane on macOS arm64 with the generated
symbol-bearing and stripped Mach-O fixtures, including cold and warm reads,
references, and cleanup. Linux, other hosts, and the larger 1.833 target matrix
were not verified with 1.989.
## DOS Ghidra analysis
`npm run verify:ghidra:dos` requires the supported Ghidra and JDK installation
-1
View File
@@ -265,7 +265,6 @@ export class KunaProvider implements AnalysisProviderCandidate {
kuna_sha256: value.sha256,
kuna_command: value.configuration.command,
kuna_executable: value.executable,
verified_release: value.verified,
jobs: value.configuration.jobs,
export: "decompile-project",
reference_graph: {
+1 -1
View File
@@ -1,5 +1,5 @@
/** Kuna release whose CLI and project layout REA verified; others report as unverified. */
export const KUNA_VERIFIED_VERSION = "1.988";
export const KUNA_VERIFIED_VERSION = "1.989";
/** Stable provider ID shared by readiness, doctor and Evidence. */
export const KUNA_PROVIDER_ID = "kuna";
+17 -2
View File
@@ -89,7 +89,7 @@ import {
kunaLimitations,
type KunaOperation,
} from "./KunaProviderCapabilities.js";
import { KUNA_PROVIDER_ID } from "./KunaRelease.js";
import { KUNA_PROVIDER_ID, KUNA_VERIFIED_VERSION } from "./KunaRelease.js";
import { KunaTargetCommands } from "./KunaTargetCommands.js";
import { KunaReferenceQueries } from "./KunaReferenceQueries.js";
import { KunaQueryWork } from "./KunaQueryWork.js";
@@ -285,6 +285,16 @@ const exportUnavailable = (
);
};
/** Version verification is observation context, not an analysis input. */
const kunaVerificationLimitations = (
installation: KunaInstallation,
): readonly string[] =>
installation.verified
? []
: [
`Kuna ${installation.version} is not REA's verified ${KUNA_VERIFIED_VERSION} release; its project layout is unverified. Version matching does not authenticate the executable.`,
];
/** Kuna names a procedure by its inventory entry or by an exact entry address. */
const procedureSelectorSchema = z.string().min(1);
@@ -652,7 +662,11 @@ export class KunaSessionClient implements AnalysisClient {
: { strings: answer.stringSource }),
},
}),
limitations: [...operationLimitations, ...answer.limitations],
limitations: [
...operationLimitations,
...kunaVerificationLimitations(installation),
...answer.limitations,
],
...(answer.relationshipCoverage === undefined
? {}
: { relationshipCoverage: answer.relationshipCoverage }),
@@ -1469,6 +1483,7 @@ export class KunaSessionClient implements AnalysisClient {
this.#deadline,
this.#heapRequest,
),
...kunaVerificationLimitations(installation),
...pseudocode.value.limitations,
KUNA_RELATIONSHIP_LIMITATION,
...(relationships.ok
@@ -89,6 +89,36 @@ test("keeps each Kuna no-pseudocode observation's limitations on its batch item"
})
).structuredContent,
);
// The provider's version policy travels with this observation, while its
// profile commits only analysis inputs. Composed and retained views must
// preserve the same warning for agents reading those results.
const verificationWarning =
"Kuna 1.833 is not REA's verified 1.989 release";
expect(batch.limitations).toContainEqual(
expect.stringContaining(verificationWarning),
);
expect(broken.analysis_profile?.parameters).not.toHaveProperty(
"verified_release",
);
const summary = parseEvidence(
(await call("analyze_function", { procedure: "main", detail: "summary" }))
.structuredContent,
);
const parent = summary.normalized_result as { parent_evidence_id: string };
const page = parseEvidence(
(
await call("inspect_analysis_view", {
source: {
kind: "retained-evidence",
evidence_id: parent.parent_evidence_id,
},
view: { kind: "native", facet: "pseudocode" },
})
).structuredContent,
);
expect(page.limitations).toContainEqual(
expect.stringContaining(verificationWarning),
);
if (batchDecompile === undefined || getEvidenceBundle === undefined)
throw new Error(
"Missing batch_decompile or get_evidence_bundle contract",
@@ -34,7 +34,7 @@ it("reports an unconfigured Kuna as a missing optional engine", async () => {
it("publishes the caller's explicit settings once the installation validates", async () => {
const root = await createTestTempDirectory("rea-kuna-doctor-");
const kuna = await writeFakeKuna(root, {
version: { stdout: "kuna 1.988\n" },
version: { stdout: "kuna 1.989\n" },
});
const cache = join(root, "cache");
@@ -48,7 +48,7 @@ it("publishes the caller's explicit settings once the installation validates", a
expect(inspection).toMatchObject({
configured: true,
available: true,
providerVersion: "1.988",
providerVersion: "1.989",
registrationEnvironment: {
REA_KUNA_COMMAND: kuna,
REA_KUNA_CACHE_DIR: cache,
@@ -59,10 +59,10 @@ it("publishes the caller's explicit settings once the installation validates", a
name: "installation",
ok: true,
detail: expect.stringMatching(
/reports Kuna 1\.988 \(sha256 [a-f0-9]{64}\)\.$/u,
/reports Kuna 1\.989 \(sha256 [a-f0-9]{64}\)\.$/u,
),
remediation: null,
details: { version: "1.988", verified_version: "1.988" },
details: { version: "1.989", verified_version: "1.989" },
},
{
name: "cache",
@@ -88,7 +88,7 @@ it("flags an unverified release without failing the installation", async () => {
"You may continue with the selected unverified release.",
),
remediation: expect.stringContaining("setting REA_KUNA_COMMAND"),
details: { verified_version: "1.988" },
details: { verified_version: "1.989" },
});
});
@@ -22,7 +22,7 @@ const it = test.skipIf(process.platform === "win32");
it("identifies a caller-selected kuna by version and digest", async () => {
const root = await createTestTempDirectory("rea-kuna-install-");
const kuna = await writeFakeKuna(root, {
version: { stdout: "kuna 1.988\n" },
version: { stdout: "kuna 1.989\n" },
});
const link = join(root, "current-kuna");
await symlink(kuna, link);
@@ -37,7 +37,7 @@ it("identifies a caller-selected kuna by version and digest", async () => {
value: {
configuration: { command: link, cacheRoot: join(root, "cache") },
executable: await realpath(kuna),
version: "1.988",
version: "1.989",
verified: true,
// A `#!` launcher reports its interpreter; ownership checks must not expect the path.
script: true,
@@ -104,14 +104,14 @@ fs.renameSync(process.argv[1] + ".new", process.argv[1]);
it("accepts another release but reports it as unverified", async () => {
const root = await createTestTempDirectory("rea-kuna-unverified-");
const kuna = await writeFakeKuna(root, {
version: { stdout: "kuna 1.986\n" },
version: { stdout: "kuna 1.988\n" },
});
const result = await inspectKunaInstallation({ REA_KUNA_COMMAND: kuna });
expect(result).toMatchObject({
ok: true,
value: { version: "1.986", verified: false },
value: { version: "1.988", verified: false },
});
});
@@ -182,22 +182,42 @@ it("waits for an aborted pre-session hash before retrying its late close owner",
it("commits the observed Kuna release and digest to the analysis profile", async () => {
const { profile, kuna } = await session("rea-kuna-profile-", {
version: { stdout: "kuna 1.988\n" },
version: { stdout: "kuna 1.989\n" },
});
expect(profile).toMatchObject({
provider: { id: "kuna", name: "Kuna", version: "1.988" },
provider: { id: "kuna", name: "Kuna", version: "1.989" },
parameters: {
kuna_version: "1.988",
kuna_version: "1.989",
kuna_sha256: expect.stringMatching(/^[0-9a-f]{64}$/u),
kuna_command: kuna,
verified_release: true,
jobs: 1,
cache_policy: "live",
},
});
});
it("reports an unverified release on observations without binding policy into the profile", async () => {
const { profile, open } = await session("rea-kuna-policy-", {
version: { stdout: "kuna 1.988\n" },
});
expect(profile.parameters).not.toHaveProperty("verified_release");
expect(profile.parameters).toMatchObject({
kuna_version: "1.988",
kuna_sha256: expect.stringMatching(/^[0-9a-f]{64}$/u),
});
const client = open();
const inventory = await run(client, "list_procedures");
const warning = "Kuna 1.988 is not REA's verified 1.989 release";
expect(inventory.limitations.join("\n")).toContain(warning);
const dossier = await run(client, "analyze_function", { procedure: "main" });
expect(dossier.limitations.join("\n")).toContain(warning);
expect(
(dossier.result as { limitations: string[] }).limitations.join("\n"),
).toContain(warning);
await client.close();
});
it("lists the selected document without starting target analysis", async () => {
const { kuna, target, cacheRoot, open } = await session(
"rea-kuna-document-metadata-",