@intentic/registry 1.176.3 → 1.209.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -6
- package/dist/registry.d.ts +92 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +117 -4
- package/dist/registry.js.map +1 -1
- package/package.json +1 -1
- package/src/registry.test.ts +158 -6
- package/src/registry.ts +200 -38
- package/src/source.test.ts +3 -1
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@ the result is shown in. Published to npm, and depended on by three otherwise-unr
|
|
|
5
5
|
whole reason it is a package: the daemon that clones a registry, the site that builds the public gallery, and
|
|
6
6
|
the [scanner](../../_tools/registry-scan) that writes the files. One zod schema instead of three that drift.
|
|
7
7
|
|
|
8
|
-
See [Publish &
|
|
9
|
-
|
|
8
|
+
See [Publish & registries](https://intentic.dev/developers/publish/) for the author-facing version of everything
|
|
9
|
+
below, and [Verification & trust](https://intentic.dev/developers/verify/) for what each trust state claims.
|
|
10
10
|
|
|
11
11
|
## The shape of it
|
|
12
12
|
|
|
@@ -23,7 +23,7 @@ lists a team's agent plugins and its intentic extensions together.
|
|
|
23
23
|
|
|
24
24
|
| File | Written by | Holds |
|
|
25
25
|
| --- | --- | --- |
|
|
26
|
-
| `.claude-plugin/marketplace.json` | humans
|
|
26
|
+
| `.claude-plugin/marketplace.json` | humans + protected admission workflow | every decision — what is listed, the exact source, trust level, and source-bound security record |
|
|
27
27
|
| `.claude-plugin/registry.generated.json` | the nightly scanner | only facts read back off the source host: stars, last push |
|
|
28
28
|
|
|
29
29
|
Keeping the derived data out of the curated file is load-bearing, not tidiness. Star counts in the hand-edited
|
|
@@ -36,13 +36,37 @@ browse wire shape, so the app's list and the website's gallery are the same rows
|
|
|
36
36
|
|
|
37
37
|
## Trust, and what each state claims
|
|
38
38
|
|
|
39
|
-
`listed`
|
|
40
|
-
|
|
41
|
-
known-malicious or known-broken, with the reason alongside.
|
|
39
|
+
In the official registry, `listed` means the exact source passed the deterministic scan and intentic agent audit,
|
|
40
|
+
but no human source review is claimed. `verified` means a human also read that same source. `blocked` means
|
|
41
|
+
known-malicious or known-broken, with the required reason alongside. A `verified` row without a
|
|
42
|
+
`securityReview`, a review copied from another repository/sha/subdirectory, or a blocked row without a reason does not parse.
|
|
43
|
+
|
|
44
|
+
`securityReview` records the repository, commit, subdirectory, both versioned policies, scanner and version,
|
|
45
|
+
timestamp, Trivy workflow run and agent-gate run. The official resolver accepts only current Trivy and Intentic
|
|
46
|
+
policies with that complete subject equal to the install pointer. A row missing either stays visible in the app
|
|
47
|
+
with install disabled and is omitted from the public gallery; it cannot become an update offer either. This is
|
|
48
|
+
the runtime backstop behind the registry's required PR check.
|
|
42
49
|
|
|
43
50
|
A blocked entry stays in the file. Deleting the row hides it from people browsing and tells the people who
|
|
44
51
|
already installed it nothing, which is backwards — they are the ones at risk. Absent on a third-party registry
|
|
45
52
|
resolves to `listed`, because a registry that doesn't use the field hasn't asserted anything.
|
|
53
|
+
Third-party registries are their own admission boundary: their non-blocked rows remain installable without
|
|
54
|
+
adopting intentic's gate or policy, and the app states when they carry no audit record.
|
|
55
|
+
|
|
56
|
+
Installed sandboxes read these states back on a daily comparison, so trust reaches the people past the browse
|
|
57
|
+
moment too: a row turned `blocked` raises an advisory on the installed extension (and, by default, switches it
|
|
58
|
+
off), and `securityFix: true` on an entry marks its pinned commit as fixing a security problem in earlier ones
|
|
59
|
+
— the installed side promotes its update badge from ambient to loud, because there the OLD version is the
|
|
60
|
+
dangerous one. Both are asserted by pull request, like `trust`, and are worth exactly that review.
|
|
61
|
+
|
|
62
|
+
## Tier, and what premium buys into
|
|
63
|
+
|
|
64
|
+
`tier` is the listing's price: `free` (the default, and the whole story for most rows) or `premium` — the
|
|
65
|
+
listing opts into the **creator pool**. A premium row needs an intentic membership, both surfaces badge it
|
|
66
|
+
before the click, and installing it **donates a published number of the member's credits to the publisher**
|
|
67
|
+
(once, deduped monthly — updates donate again at most monthly). No usage is metered or reported anywhere;
|
|
68
|
+
the deliberate install is the whole signal. The economics live in
|
|
69
|
+
[The creator pool](https://intentic.dev/earn/).
|
|
46
70
|
|
|
47
71
|
## The mark
|
|
48
72
|
|
|
@@ -75,6 +99,12 @@ because extension code runs trusted in the owner's browser and a branch name is
|
|
|
75
99
|
break with a force-push. A registry entry without one still lists and still reads; it just can't be installed
|
|
76
100
|
in a click.
|
|
77
101
|
|
|
102
|
+
The sha is necessary but does not confine the code. A browser entry runs in the app's JavaScript realm with
|
|
103
|
+
page, browser-storage and network access; server/process/agent/bin/environment contributions have their own
|
|
104
|
+
execution surfaces. The manifest gates cooperative calls through the extension API, not arbitrary browser
|
|
105
|
+
behavior. Deterministic scanning catches known dependency, secret and configuration findings; the agent audit
|
|
106
|
+
exists because neither those signatures nor manifest validation can answer malicious intent.
|
|
107
|
+
|
|
78
108
|
## Key files
|
|
79
109
|
|
|
80
110
|
- [src/registry.ts](src/registry.ts) — the file format: what a registry repo contains.
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,14 +1,41 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { type RegistryInstall } from "./source.js";
|
|
2
3
|
export declare const REGISTRY_FILE = ".claude-plugin/marketplace.json";
|
|
3
4
|
export declare const REGISTRY_FACTS_FILE = ".claude-plugin/registry.generated.json";
|
|
4
5
|
export declare const OFFICIAL_REGISTRY_URL = "https://github.com/intentic/registry";
|
|
5
6
|
export declare const REGISTRY_TOPIC = "intentic-extension";
|
|
7
|
+
export declare const OFFICIAL_SECURITY_REVIEW_POLICY = "intentic-extension-security-v1";
|
|
8
|
+
export declare const OFFICIAL_SECURITY_REVIEWER = "intentic-agent-gate";
|
|
9
|
+
export declare const OFFICIAL_DETERMINISTIC_SCAN_POLICY = "intentic-extension-deterministic-v1";
|
|
10
|
+
export declare const OFFICIAL_DETERMINISTIC_SCANNER = "trivy";
|
|
11
|
+
export declare const OFFICIAL_DETERMINISTIC_SCANNER_VERSION = "0.72.0";
|
|
12
|
+
export declare const RegistrySecurityReviewSchema: z.ZodObject<{
|
|
13
|
+
sha: z.ZodString;
|
|
14
|
+
url: z.ZodString;
|
|
15
|
+
path: z.ZodOptional<z.ZodString>;
|
|
16
|
+
policy: z.ZodString;
|
|
17
|
+
reviewer: z.ZodString;
|
|
18
|
+
reviewedAt: z.ZodISODateTime;
|
|
19
|
+
runId: z.ZodString;
|
|
20
|
+
deterministic: z.ZodObject<{
|
|
21
|
+
policy: z.ZodString;
|
|
22
|
+
scanner: z.ZodString;
|
|
23
|
+
version: z.ZodString;
|
|
24
|
+
runId: z.ZodString;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
}, z.core.$strip>;
|
|
27
|
+
export type RegistrySecurityReview = z.infer<typeof RegistrySecurityReviewSchema>;
|
|
6
28
|
export declare const RegistryTrustSchema: z.ZodEnum<{
|
|
7
29
|
blocked: "blocked";
|
|
8
30
|
listed: "listed";
|
|
9
31
|
verified: "verified";
|
|
10
32
|
}>;
|
|
11
33
|
export type RegistryTrust = z.infer<typeof RegistryTrustSchema>;
|
|
34
|
+
export declare const RegistryTierSchema: z.ZodEnum<{
|
|
35
|
+
free: "free";
|
|
36
|
+
premium: "premium";
|
|
37
|
+
}>;
|
|
38
|
+
export type RegistryTier = z.infer<typeof RegistryTierSchema>;
|
|
12
39
|
export declare const RegistryFileSchema: z.ZodObject<{
|
|
13
40
|
name: z.ZodString;
|
|
14
41
|
metadata: z.ZodOptional<z.ZodObject<{
|
|
@@ -28,7 +55,28 @@ export declare const RegistryFileSchema: z.ZodObject<{
|
|
|
28
55
|
verified: "verified";
|
|
29
56
|
}>>;
|
|
30
57
|
trustReason: z.ZodOptional<z.ZodString>;
|
|
58
|
+
securityReview: z.ZodOptional<z.ZodObject<{
|
|
59
|
+
sha: z.ZodString;
|
|
60
|
+
url: z.ZodString;
|
|
61
|
+
path: z.ZodOptional<z.ZodString>;
|
|
62
|
+
policy: z.ZodString;
|
|
63
|
+
reviewer: z.ZodString;
|
|
64
|
+
reviewedAt: z.ZodISODateTime;
|
|
65
|
+
runId: z.ZodString;
|
|
66
|
+
deterministic: z.ZodObject<{
|
|
67
|
+
policy: z.ZodString;
|
|
68
|
+
scanner: z.ZodString;
|
|
69
|
+
version: z.ZodString;
|
|
70
|
+
runId: z.ZodString;
|
|
71
|
+
}, z.core.$strip>;
|
|
72
|
+
}, z.core.$strip>>;
|
|
73
|
+
securityFix: z.ZodOptional<z.ZodBoolean>;
|
|
74
|
+
tier: z.ZodOptional<z.ZodEnum<{
|
|
75
|
+
free: "free";
|
|
76
|
+
premium: "premium";
|
|
77
|
+
}>>;
|
|
31
78
|
category: z.ZodOptional<z.ZodString>;
|
|
79
|
+
art: z.ZodOptional<z.ZodString>;
|
|
32
80
|
logo: z.ZodOptional<z.ZodString>;
|
|
33
81
|
icon: z.ZodOptional<z.ZodString>;
|
|
34
82
|
homepage: z.ZodOptional<z.ZodURL>;
|
|
@@ -36,12 +84,25 @@ export declare const RegistryFileSchema: z.ZodObject<{
|
|
|
36
84
|
}, z.core.$strip>>;
|
|
37
85
|
}, z.core.$strip>;
|
|
38
86
|
export type RegistryFile = z.infer<typeof RegistryFileSchema>;
|
|
87
|
+
declare const RegistryChecksSchema: z.ZodObject<{
|
|
88
|
+
sha: z.ZodString;
|
|
89
|
+
manifest: z.ZodString;
|
|
90
|
+
bundle: z.ZodString;
|
|
91
|
+
engines: z.ZodOptional<z.ZodString>;
|
|
92
|
+
}, z.core.$strip>;
|
|
93
|
+
export type RegistryChecks = z.infer<typeof RegistryChecksSchema>;
|
|
39
94
|
export declare const RegistryFactsSchema: z.ZodObject<{
|
|
40
95
|
scannedAt: z.ZodString;
|
|
41
96
|
entries: z.ZodArray<z.ZodObject<{
|
|
42
97
|
name: z.ZodString;
|
|
43
98
|
stars: z.ZodOptional<z.ZodNumber>;
|
|
44
99
|
pushedAt: z.ZodOptional<z.ZodString>;
|
|
100
|
+
checks: z.ZodOptional<z.ZodObject<{
|
|
101
|
+
sha: z.ZodString;
|
|
102
|
+
manifest: z.ZodString;
|
|
103
|
+
bundle: z.ZodString;
|
|
104
|
+
engines: z.ZodOptional<z.ZodString>;
|
|
105
|
+
}, z.core.$strip>>;
|
|
45
106
|
}, z.core.$strip>>;
|
|
46
107
|
}, z.core.$strip>;
|
|
47
108
|
export type RegistryFacts = z.infer<typeof RegistryFactsSchema>;
|
|
@@ -59,7 +120,29 @@ export declare const RegistryEntrySchema: z.ZodObject<{
|
|
|
59
120
|
verified: "verified";
|
|
60
121
|
}>;
|
|
61
122
|
trustReason: z.ZodOptional<z.ZodString>;
|
|
123
|
+
securityReview: z.ZodOptional<z.ZodObject<{
|
|
124
|
+
sha: z.ZodString;
|
|
125
|
+
url: z.ZodString;
|
|
126
|
+
path: z.ZodOptional<z.ZodString>;
|
|
127
|
+
policy: z.ZodString;
|
|
128
|
+
reviewer: z.ZodString;
|
|
129
|
+
reviewedAt: z.ZodISODateTime;
|
|
130
|
+
runId: z.ZodString;
|
|
131
|
+
deterministic: z.ZodObject<{
|
|
132
|
+
policy: z.ZodString;
|
|
133
|
+
scanner: z.ZodString;
|
|
134
|
+
version: z.ZodString;
|
|
135
|
+
runId: z.ZodString;
|
|
136
|
+
}, z.core.$strip>;
|
|
137
|
+
}, z.core.$strip>>;
|
|
138
|
+
admitted: z.ZodBoolean;
|
|
139
|
+
securityFix: z.ZodOptional<z.ZodBoolean>;
|
|
140
|
+
tier: z.ZodEnum<{
|
|
141
|
+
free: "free";
|
|
142
|
+
premium: "premium";
|
|
143
|
+
}>;
|
|
62
144
|
category: z.ZodOptional<z.ZodString>;
|
|
145
|
+
art: z.ZodOptional<z.ZodString>;
|
|
63
146
|
logo: z.ZodOptional<z.ZodString>;
|
|
64
147
|
icon: z.ZodOptional<z.ZodString>;
|
|
65
148
|
homepage: z.ZodOptional<z.ZodString>;
|
|
@@ -70,8 +153,17 @@ export declare const RegistryEntrySchema: z.ZodObject<{
|
|
|
70
153
|
}, z.core.$strip>>;
|
|
71
154
|
stars: z.ZodOptional<z.ZodNumber>;
|
|
72
155
|
pushedAt: z.ZodOptional<z.ZodString>;
|
|
156
|
+
checks: z.ZodOptional<z.ZodObject<{
|
|
157
|
+
sha: z.ZodString;
|
|
158
|
+
manifest: z.ZodString;
|
|
159
|
+
bundle: z.ZodString;
|
|
160
|
+
engines: z.ZodOptional<z.ZodString>;
|
|
161
|
+
}, z.core.$strip>>;
|
|
73
162
|
}, z.core.$strip>;
|
|
74
163
|
export type RegistryEntry = z.infer<typeof RegistryEntrySchema>;
|
|
164
|
+
export declare const isOfficialRegistryUrl: (value: string) => boolean;
|
|
165
|
+
export declare const isCurrentSecurityReview: (review: RegistrySecurityReview | undefined, install: RegistryInstall | undefined) => boolean;
|
|
75
166
|
export declare const resolveRegistry: (file: RegistryFile, facts: RegistryFacts | undefined, registryUrl: string) => RegistryEntry[];
|
|
76
167
|
export declare const compareEntries: (a: RegistryEntry, b: RegistryEntry) => number;
|
|
168
|
+
export {};
|
|
77
169
|
//# sourceMappingURL=registry.d.ts.map
|
package/dist/registry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,KAAK,eAAe,EAAwC,MAAM,aAAa,CAAC;AAkBzF,eAAO,MAAM,aAAa,oCAAoC,CAAC;AAC/D,eAAO,MAAM,mBAAmB,2CAA2C,CAAC;AAI5E,eAAO,MAAM,qBAAqB,yCAAyC,CAAC;AAI5E,eAAO,MAAM,cAAc,uBAAuB,CAAC;AAOnD,eAAO,MAAM,+BAA+B,mCAAmC,CAAC;AAChF,eAAO,MAAM,0BAA0B,wBAAwB,CAAC;AAChE,eAAO,MAAM,kCAAkC,wCAAwC,CAAC;AACxF,eAAO,MAAM,8BAA8B,UAAU,CAAC;AACtD,eAAO,MAAM,sCAAsC,WAAW,CAAC;AAI/D,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;iBAcvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAalF,eAAO,MAAM,mBAAmB;;;;EAA4C,CAAC;AAC7E,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAQhE,eAAO,MAAM,kBAAkB;;;EAA8B,CAAC;AAC9D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAgE9D,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiBzB,CAAC;AACP,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAW9D,QAAA,MAAM,oBAAoB;;;;;iBASxB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAWlE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;iBAG9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAIhE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2B9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAsBhE,eAAO,MAAM,qBAAqB,UAAW,MAAM,KAAG,OAAwF,CAAC;AAE/I,eAAO,MAAM,uBAAuB,WAAY,sBAAsB,GAAG,SAAS,WAAW,eAAe,GAAG,SAAS,KAAG,OAShD,CAAC;AAI5E,eAAO,MAAM,eAAe,SAAU,YAAY,SAAS,aAAa,GAAG,SAAS,eAAe,MAAM,KAAG,aAAa,EAkCxH,CAAC;AASF,eAAO,MAAM,cAAc,MAAO,aAAa,KAAK,aAAa,KAAG,MAWnE,CAAC"}
|
package/dist/registry.js
CHANGED
|
@@ -4,29 +4,96 @@ export const REGISTRY_FILE = ".claude-plugin/marketplace.json";
|
|
|
4
4
|
export const REGISTRY_FACTS_FILE = ".claude-plugin/registry.generated.json";
|
|
5
5
|
export const OFFICIAL_REGISTRY_URL = "https://github.com/intentic/registry";
|
|
6
6
|
export const REGISTRY_TOPIC = "intentic-extension";
|
|
7
|
+
export const OFFICIAL_SECURITY_REVIEW_POLICY = "intentic-extension-security-v1";
|
|
8
|
+
export const OFFICIAL_SECURITY_REVIEWER = "intentic-agent-gate";
|
|
9
|
+
export const OFFICIAL_DETERMINISTIC_SCAN_POLICY = "intentic-extension-deterministic-v1";
|
|
10
|
+
export const OFFICIAL_DETERMINISTIC_SCANNER = "trivy";
|
|
11
|
+
export const OFFICIAL_DETERMINISTIC_SCANNER_VERSION = "0.72.0";
|
|
12
|
+
const FULL_SHA = /^[0-9a-f]{40}$/;
|
|
13
|
+
export const RegistrySecurityReviewSchema = z.object({
|
|
14
|
+
sha: z.string().regex(FULL_SHA, "must be a full lowercase commit sha"),
|
|
15
|
+
url: z.string().min(1),
|
|
16
|
+
path: z.string().min(1).optional(),
|
|
17
|
+
policy: z.string().min(1),
|
|
18
|
+
reviewer: z.string().min(1),
|
|
19
|
+
reviewedAt: z.iso.datetime(),
|
|
20
|
+
runId: z.string().min(1),
|
|
21
|
+
deterministic: z.object({
|
|
22
|
+
policy: z.string().min(1),
|
|
23
|
+
scanner: z.string().min(1),
|
|
24
|
+
version: z.string().min(1),
|
|
25
|
+
runId: z.string().min(1),
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
7
28
|
export const RegistryTrustSchema = z.enum(["verified", "listed", "blocked"]);
|
|
8
|
-
const
|
|
29
|
+
export const RegistryTierSchema = z.enum(["free", "premium"]);
|
|
30
|
+
const RegistryFileEntrySchema = z
|
|
31
|
+
.object({
|
|
9
32
|
name: z.string(),
|
|
10
33
|
description: z.string().optional(),
|
|
11
34
|
version: z.string().optional(),
|
|
12
35
|
kind: z.enum(["plugin", "extension"]).optional(),
|
|
13
36
|
trust: RegistryTrustSchema.optional(),
|
|
14
37
|
trustReason: z.string().optional(),
|
|
38
|
+
securityReview: RegistrySecurityReviewSchema.optional(),
|
|
39
|
+
securityFix: z.boolean().optional(),
|
|
40
|
+
tier: RegistryTierSchema.optional(),
|
|
15
41
|
category: z.string().optional(),
|
|
42
|
+
art: z.string().max(4096).optional(),
|
|
16
43
|
logo: z.string().optional(),
|
|
17
44
|
icon: z.string().optional(),
|
|
18
45
|
homepage: z.url().optional(),
|
|
19
46
|
source: z.unknown(),
|
|
47
|
+
})
|
|
48
|
+
.superRefine((entry, ctx) => {
|
|
49
|
+
const trust = entry.trust ?? "listed";
|
|
50
|
+
if (trust === "blocked" && (entry.trustReason === undefined || entry.trustReason.trim() === "")) {
|
|
51
|
+
ctx.addIssue({ code: "custom", path: ["trustReason"], message: "a blocked entry must say why" });
|
|
52
|
+
}
|
|
53
|
+
if (trust === "verified" && entry.securityReview === undefined) {
|
|
54
|
+
ctx.addIssue({ code: "custom", path: ["securityReview"], message: "a verified entry must carry its security review" });
|
|
55
|
+
}
|
|
56
|
+
if (entry.securityReview === undefined) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const install = resolveSource(entry.source, "", undefined);
|
|
60
|
+
if (install?.ref !== entry.securityReview.sha || install?.url !== entry.securityReview.url || install.path !== entry.securityReview.path) {
|
|
61
|
+
ctx.addIssue({
|
|
62
|
+
code: "custom",
|
|
63
|
+
path: ["securityReview"],
|
|
64
|
+
message: "must equal the exact repository, commit and subdirectory named by source",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
20
67
|
});
|
|
21
|
-
export const RegistryFileSchema = z
|
|
68
|
+
export const RegistryFileSchema = z
|
|
69
|
+
.object({
|
|
22
70
|
name: z.string(),
|
|
23
71
|
metadata: z.object({ pluginRoot: z.string().optional() }).optional(),
|
|
24
72
|
plugins: z.array(RegistryFileEntrySchema),
|
|
73
|
+
})
|
|
74
|
+
.superRefine((file, ctx) => {
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
for (let at = 0; at < file.plugins.length; at += 1) {
|
|
77
|
+
const name = file.plugins[at]?.name;
|
|
78
|
+
if (name !== undefined && seen.has(name)) {
|
|
79
|
+
ctx.addIssue({ code: "custom", path: ["plugins", at, "name"], message: "entry names must be unique" });
|
|
80
|
+
}
|
|
81
|
+
if (name !== undefined) {
|
|
82
|
+
seen.add(name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const RegistryChecksSchema = z.object({
|
|
87
|
+
sha: z.string(),
|
|
88
|
+
manifest: z.string(),
|
|
89
|
+
bundle: z.string(),
|
|
90
|
+
engines: z.string().optional(),
|
|
25
91
|
});
|
|
26
92
|
const RegistryFactsEntrySchema = z.object({
|
|
27
93
|
name: z.string(),
|
|
28
94
|
stars: z.number().int().nonnegative().optional(),
|
|
29
95
|
pushedAt: z.string().optional(),
|
|
96
|
+
checks: RegistryChecksSchema.optional(),
|
|
30
97
|
});
|
|
31
98
|
export const RegistryFactsSchema = z.object({
|
|
32
99
|
scannedAt: z.string(),
|
|
@@ -39,33 +106,79 @@ export const RegistryEntrySchema = z.object({
|
|
|
39
106
|
kind: z.enum(["plugin", "extension"]),
|
|
40
107
|
trust: RegistryTrustSchema,
|
|
41
108
|
trustReason: z.string().optional(),
|
|
109
|
+
securityReview: RegistrySecurityReviewSchema.optional(),
|
|
110
|
+
admitted: z.boolean(),
|
|
111
|
+
securityFix: z.boolean().optional(),
|
|
112
|
+
tier: RegistryTierSchema,
|
|
42
113
|
category: z.string().optional(),
|
|
114
|
+
art: z.string().optional(),
|
|
43
115
|
logo: z.string().optional(),
|
|
44
116
|
icon: z.string().optional(),
|
|
45
117
|
homepage: z.string().optional(),
|
|
46
118
|
install: RegistryInstallSchema.optional(),
|
|
47
119
|
stars: z.number().int().nonnegative().optional(),
|
|
48
120
|
pushedAt: z.string().optional(),
|
|
121
|
+
checks: RegistryChecksSchema.optional(),
|
|
49
122
|
});
|
|
123
|
+
const normalizedRegistryUrl = (value) => {
|
|
124
|
+
const scp = value.includes("://") ? null : /^(?:[^@]+@)?([^:]+):\/?(.+)$/u.exec(value);
|
|
125
|
+
if (scp !== null) {
|
|
126
|
+
return `${scp[1]?.toLowerCase()}/${scp[2]
|
|
127
|
+
?.replace(/\.git\/?$/u, "")
|
|
128
|
+
.replace(/\/$/u, "")
|
|
129
|
+
.toLowerCase()}`;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const url = new URL(value);
|
|
133
|
+
const path = url.pathname
|
|
134
|
+
.replace(/\.git\/?$/u, "")
|
|
135
|
+
.replace(/\/$/u, "")
|
|
136
|
+
.toLowerCase();
|
|
137
|
+
return `${url.host.toLowerCase()}${path}`;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
export const isOfficialRegistryUrl = (value) => normalizedRegistryUrl(value) === normalizedRegistryUrl(OFFICIAL_REGISTRY_URL);
|
|
144
|
+
export const isCurrentSecurityReview = (review, install) => review !== undefined &&
|
|
145
|
+
review.sha === install?.ref &&
|
|
146
|
+
review.url === install.url &&
|
|
147
|
+
review.path === install.path &&
|
|
148
|
+
review.policy === OFFICIAL_SECURITY_REVIEW_POLICY &&
|
|
149
|
+
review.reviewer === OFFICIAL_SECURITY_REVIEWER &&
|
|
150
|
+
review.deterministic.policy === OFFICIAL_DETERMINISTIC_SCAN_POLICY &&
|
|
151
|
+
review.deterministic.scanner === OFFICIAL_DETERMINISTIC_SCANNER &&
|
|
152
|
+
review.deterministic.version === OFFICIAL_DETERMINISTIC_SCANNER_VERSION;
|
|
50
153
|
export const resolveRegistry = (file, facts, registryUrl) => {
|
|
51
154
|
const byName = new Map(facts?.entries.map((entry) => [entry.name, entry]) ?? []);
|
|
155
|
+
const official = isOfficialRegistryUrl(registryUrl);
|
|
52
156
|
return file.plugins.map((plugin) => {
|
|
53
157
|
const upstream = byName.get(plugin.name);
|
|
54
158
|
const install = resolveSource(plugin.source, registryUrl, file.metadata?.pluginRoot);
|
|
159
|
+
const kind = plugin.kind ?? "plugin";
|
|
160
|
+
const trust = plugin.trust ?? "listed";
|
|
161
|
+
const admitted = trust !== "blocked" && (kind !== "extension" || !official || isCurrentSecurityReview(plugin.securityReview, install));
|
|
55
162
|
return {
|
|
56
163
|
name: plugin.name,
|
|
57
|
-
kind
|
|
58
|
-
trust
|
|
164
|
+
kind,
|
|
165
|
+
trust,
|
|
166
|
+
admitted,
|
|
167
|
+
tier: plugin.tier ?? "free",
|
|
59
168
|
...(plugin.description !== undefined ? { description: plugin.description } : {}),
|
|
60
169
|
...(plugin.version !== undefined ? { version: plugin.version } : {}),
|
|
61
170
|
...(plugin.trustReason !== undefined ? { trustReason: plugin.trustReason } : {}),
|
|
171
|
+
...(plugin.securityReview !== undefined ? { securityReview: plugin.securityReview } : {}),
|
|
172
|
+
...(plugin.securityFix !== undefined ? { securityFix: plugin.securityFix } : {}),
|
|
62
173
|
...(plugin.category !== undefined ? { category: plugin.category } : {}),
|
|
174
|
+
...(plugin.art !== undefined ? { art: plugin.art } : {}),
|
|
63
175
|
...(plugin.logo !== undefined ? { logo: plugin.logo } : {}),
|
|
64
176
|
...(plugin.icon !== undefined ? { icon: plugin.icon } : {}),
|
|
65
177
|
...(plugin.homepage !== undefined ? { homepage: plugin.homepage } : {}),
|
|
66
178
|
...(install !== undefined ? { install } : {}),
|
|
67
179
|
...(upstream?.stars !== undefined ? { stars: upstream.stars } : {}),
|
|
68
180
|
...(upstream?.pushedAt !== undefined ? { pushedAt: upstream.pushedAt } : {}),
|
|
181
|
+
...(upstream?.checks !== undefined && upstream.checks.sha === install?.ref ? { checks: upstream.checks } : {}),
|
|
69
182
|
};
|
|
70
183
|
});
|
|
71
184
|
};
|
package/dist/registry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentic/registry",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.209.1",
|
|
4
4
|
"description": "The extension-registry file format — a git repo of sha-pinned pointers, shared by the daemon, the site gallery and the scanner",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/registry.test.ts
CHANGED
|
@@ -1,15 +1,53 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
compareEntries,
|
|
4
|
+
isOfficialRegistryUrl,
|
|
5
|
+
OFFICIAL_DETERMINISTIC_SCANNER,
|
|
6
|
+
OFFICIAL_DETERMINISTIC_SCANNER_VERSION,
|
|
7
|
+
OFFICIAL_DETERMINISTIC_SCAN_POLICY,
|
|
8
|
+
OFFICIAL_SECURITY_REVIEW_POLICY,
|
|
9
|
+
OFFICIAL_SECURITY_REVIEWER,
|
|
10
|
+
type RegistryEntry,
|
|
11
|
+
RegistryFactsSchema,
|
|
12
|
+
RegistryFileSchema,
|
|
13
|
+
resolveRegistry,
|
|
14
|
+
} from "./registry.js";
|
|
3
15
|
|
|
4
16
|
const SHA = "9f2c1ab0d4e5f60718293a4b5c6d7e8f90a1b2c3";
|
|
5
17
|
const REGISTRY = "https://github.com/intentic/registry";
|
|
18
|
+
const REVIEW = {
|
|
19
|
+
sha: SHA,
|
|
20
|
+
url: `https://github.com/acme/incidents.git`,
|
|
21
|
+
policy: OFFICIAL_SECURITY_REVIEW_POLICY,
|
|
22
|
+
reviewer: OFFICIAL_SECURITY_REVIEWER,
|
|
23
|
+
reviewedAt: "2026-08-01T00:00:00.000Z",
|
|
24
|
+
runId: "run-1",
|
|
25
|
+
deterministic: {
|
|
26
|
+
policy: OFFICIAL_DETERMINISTIC_SCAN_POLICY,
|
|
27
|
+
scanner: OFFICIAL_DETERMINISTIC_SCANNER,
|
|
28
|
+
version: OFFICIAL_DETERMINISTIC_SCANNER_VERSION,
|
|
29
|
+
runId: `workflow-1`,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
6
32
|
|
|
7
33
|
const file = RegistryFileSchema.parse({
|
|
8
34
|
name: `intentic`,
|
|
9
35
|
plugins: [
|
|
10
|
-
{
|
|
36
|
+
{
|
|
37
|
+
name: `incidents`,
|
|
38
|
+
kind: `extension`,
|
|
39
|
+
trust: `verified`,
|
|
40
|
+
securityReview: REVIEW,
|
|
41
|
+
source: { source: `github`, repo: `acme/incidents`, sha: SHA },
|
|
42
|
+
},
|
|
11
43
|
{ name: `linear`, kind: `extension`, source: { source: `github`, repo: `acme/linear`, sha: SHA } },
|
|
12
|
-
{
|
|
44
|
+
{
|
|
45
|
+
name: `evil`,
|
|
46
|
+
kind: `extension`,
|
|
47
|
+
trust: `blocked`,
|
|
48
|
+
trustReason: `exfiltrates workspace secrets`,
|
|
49
|
+
source: { source: `github`, repo: `bad/evil` },
|
|
50
|
+
},
|
|
13
51
|
],
|
|
14
52
|
});
|
|
15
53
|
|
|
@@ -30,12 +68,68 @@ describe(`resolveRegistry`, () => {
|
|
|
30
68
|
expect(resolveRegistry(file, undefined, REGISTRY)).toHaveLength(3);
|
|
31
69
|
});
|
|
32
70
|
|
|
33
|
-
it(`
|
|
71
|
+
it(`admits official extensions only when both automated checks match their complete source identity`, () => {
|
|
72
|
+
const entries = resolveRegistry(file, undefined, REGISTRY);
|
|
73
|
+
expect(entries.find((entry) => entry.name === `incidents`)?.admitted).toBe(true);
|
|
74
|
+
expect(entries.find((entry) => entry.name === `linear`)?.admitted).toBe(false);
|
|
75
|
+
expect(entries.find((entry) => entry.name === `evil`)?.admitted).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it(`does not admit deterministic evidence from an old policy or scanner version`, () => {
|
|
79
|
+
for (const deterministic of [
|
|
80
|
+
{ ...REVIEW.deterministic, policy: `intentic-extension-deterministic-v0` },
|
|
81
|
+
{ ...REVIEW.deterministic, version: `0.71.0` },
|
|
82
|
+
]) {
|
|
83
|
+
const registry = RegistryFileSchema.parse({
|
|
84
|
+
name: `intentic`,
|
|
85
|
+
plugins: [
|
|
86
|
+
{
|
|
87
|
+
name: `incidents`,
|
|
88
|
+
kind: `extension`,
|
|
89
|
+
securityReview: { ...REVIEW, deterministic },
|
|
90
|
+
source: { source: `github`, repo: `acme/incidents`, sha: SHA },
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
});
|
|
94
|
+
expect(resolveRegistry(registry, undefined, REGISTRY)[0]?.admitted).toBe(false);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it(`leaves a third-party registry as its own trust boundary`, () => {
|
|
99
|
+
const entries = resolveRegistry(file, undefined, `https://github.com/acme/internal-registry`);
|
|
100
|
+
expect(entries.find((entry) => entry.name === `linear`)?.admitted).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it(`defaults an unstated trust to listed, an unstated kind to plugin, and an unstated tier to free`, () => {
|
|
34
104
|
const entries = resolveRegistry(RegistryFileSchema.parse({ name: `x`, plugins: [{ name: `a`, source: `./a` }] }), undefined, REGISTRY);
|
|
35
|
-
expect(entries[0]).toMatchObject({ trust: `listed`, kind: `plugin` });
|
|
105
|
+
expect(entries[0]).toMatchObject({ trust: `listed`, kind: `plugin`, tier: `free` });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it(`carries a premium tier through the resolve`, () => {
|
|
109
|
+
const premium = RegistryFileSchema.parse({
|
|
110
|
+
name: `x`,
|
|
111
|
+
plugins: [{ name: `acme.research`, kind: `extension`, tier: `premium`, source: { source: `github`, repo: `acme/research`, sha: SHA } }],
|
|
112
|
+
});
|
|
113
|
+
expect(resolveRegistry(premium, undefined, REGISTRY)[0]?.tier).toBe(`premium`);
|
|
36
114
|
});
|
|
37
115
|
|
|
38
116
|
// Blocked rows must survive the resolve — deleting them is what hides a warning from the people who
|
|
117
|
+
/* The staleness rule the checks ride on: a fact is bound to the sha it was derived from, and a listing
|
|
118
|
+
* repointed since the last scan renders no checks at all — the honest gap — rather than yesterday's verdict
|
|
119
|
+
* describing today's pointer. */
|
|
120
|
+
it(`joins checks only when they were derived from the sha the listing still pins`, () => {
|
|
121
|
+
const facts = RegistryFactsSchema.parse({
|
|
122
|
+
scannedAt: `2026-08-01T00:00:00.000Z`,
|
|
123
|
+
entries: [
|
|
124
|
+
{ name: `linear`, checks: { sha: SHA, manifest: `ok`, bundle: `ok`, engines: `^2.0.0` } },
|
|
125
|
+
{ name: `incidents`, checks: { sha: `b`.repeat(40), manifest: `ok`, bundle: `ok` } },
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
const entries = resolveRegistry(file, facts, REGISTRY);
|
|
129
|
+
expect(entries.find((entry) => entry.name === `linear`)?.checks).toEqual({ sha: SHA, manifest: `ok`, bundle: `ok`, engines: `^2.0.0` });
|
|
130
|
+
expect(entries.find((entry) => entry.name === `incidents`)?.checks).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
|
|
39
133
|
// already installed the thing.
|
|
40
134
|
it(`keeps a blocked entry, with its reason`, () => {
|
|
41
135
|
const blocked = resolveRegistry(file, undefined, REGISTRY).find((entry) => entry.name === `evil`);
|
|
@@ -44,7 +138,14 @@ describe(`resolveRegistry`, () => {
|
|
|
44
138
|
});
|
|
45
139
|
|
|
46
140
|
describe(`compareEntries`, () => {
|
|
47
|
-
const entry = (over: Partial<RegistryEntry>): RegistryEntry => ({
|
|
141
|
+
const entry = (over: Partial<RegistryEntry>): RegistryEntry => ({
|
|
142
|
+
name: `x`,
|
|
143
|
+
kind: `extension`,
|
|
144
|
+
trust: `listed`,
|
|
145
|
+
admitted: true,
|
|
146
|
+
tier: `free`,
|
|
147
|
+
...over,
|
|
148
|
+
});
|
|
48
149
|
|
|
49
150
|
it(`puts verified first even when it has fewer stars`, () => {
|
|
50
151
|
const sorted = [entry({ name: `popular`, stars: 900 }), entry({ name: `checked`, trust: `verified`, stars: 1 })].toSorted(compareEntries);
|
|
@@ -66,3 +167,54 @@ describe(`compareEntries`, () => {
|
|
|
66
167
|
expect(sorted.map((e) => e.name)).toEqual([`fresh`, `stale`, `undated`]);
|
|
67
168
|
});
|
|
68
169
|
});
|
|
170
|
+
|
|
171
|
+
describe(`security review invariants`, () => {
|
|
172
|
+
it(`refuses a verified row with no evidence and a blocked row with no reason`, () => {
|
|
173
|
+
expect(
|
|
174
|
+
RegistryFileSchema.safeParse({
|
|
175
|
+
name: `x`,
|
|
176
|
+
plugins: [{ name: `verified`, kind: `extension`, trust: `verified`, source: { source: `github`, repo: `x/v`, sha: SHA } }],
|
|
177
|
+
}).success,
|
|
178
|
+
).toBe(false);
|
|
179
|
+
expect(
|
|
180
|
+
RegistryFileSchema.safeParse({
|
|
181
|
+
name: `x`,
|
|
182
|
+
plugins: [{ name: `blocked`, kind: `extension`, trust: `blocked`, source: { source: `github`, repo: `x/b`, sha: SHA } }],
|
|
183
|
+
}).success,
|
|
184
|
+
).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it(`refuses evidence copied from another repository or subdirectory at the same sha`, () => {
|
|
188
|
+
for (const entry of [
|
|
189
|
+
{
|
|
190
|
+
name: `changed-repository`,
|
|
191
|
+
kind: `extension`,
|
|
192
|
+
trust: `listed`,
|
|
193
|
+
securityReview: REVIEW,
|
|
194
|
+
source: { source: `github`, repo: `x/changed`, sha: SHA },
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: `changed-path`,
|
|
198
|
+
kind: `extension`,
|
|
199
|
+
trust: `listed`,
|
|
200
|
+
securityReview: { ...REVIEW, url: `https://github.com/acme/monorepo.git`, path: `packages/other` },
|
|
201
|
+
source: {
|
|
202
|
+
source: `git-subdir`,
|
|
203
|
+
url: `https://github.com/acme/monorepo.git`,
|
|
204
|
+
path: `packages/changed`,
|
|
205
|
+
sha: SHA,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
]) {
|
|
209
|
+
expect(RegistryFileSchema.safeParse({ name: `x`, plugins: [entry] }).success).toBe(false);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it(`recognizes harmless spellings of the official registry so they cannot bypass admission`, () => {
|
|
214
|
+
expect(isOfficialRegistryUrl(`https://github.com/intentic/registry.git/`)).toBe(true);
|
|
215
|
+
expect(isOfficialRegistryUrl(`http://github.com/intentic/registry`)).toBe(true);
|
|
216
|
+
expect(isOfficialRegistryUrl(`git@github.com:intentic/registry.git`)).toBe(true);
|
|
217
|
+
expect(isOfficialRegistryUrl(`https://github.com/INTENTIC/REGISTRY`)).toBe(true);
|
|
218
|
+
expect(isOfficialRegistryUrl(`https://github.com/intentic/registry-copy`)).toBe(false);
|
|
219
|
+
});
|
|
220
|
+
});
|
package/src/registry.ts
CHANGED
|
@@ -10,8 +10,8 @@ import { type RegistryInstall, RegistryInstallSchema, resolveSource } from "./so
|
|
|
10
10
|
* own fields and Claude Code ignores what it doesn't know, so one repo serves both consumers.
|
|
11
11
|
*
|
|
12
12
|
* The split into TWO files is the load-bearing part. `marketplace.json` is hand-edited and is the only place a
|
|
13
|
-
*
|
|
14
|
-
* off the source host. Keeping star counts out of the curated file is what stops every nightly refresh from
|
|
13
|
+
* curated decision and source-bound admission are recorded; `registry.generated.json` is bot-written and holds
|
|
14
|
+
* nothing but facts read back off the source host. Keeping star counts out of the curated file is what stops every nightly refresh from
|
|
15
15
|
* being a merge conflict against an open pull request — and it keeps the review diff to the thing being
|
|
16
16
|
* decided. A registry that carries no generated file is a registry with no stars, which renders fine. */
|
|
17
17
|
|
|
@@ -28,11 +28,41 @@ export const OFFICIAL_REGISTRY_URL = "https://github.com/intentic/registry";
|
|
|
28
28
|
// request against the registry, never a listing.
|
|
29
29
|
export const REGISTRY_TOPIC = "intentic-extension";
|
|
30
30
|
|
|
31
|
+
/* THE ADMISSION RECORD for the official registry. Full results live in Trivy and intentic; this is the small,
|
|
32
|
+
* durable fact a registry row needs in order to bind both verdicts to what an installer will actually clone.
|
|
33
|
+
* Policies and scanner version are explicit because changing any admission control must make yesterday's
|
|
34
|
+
* reviews visibly stale rather than silently pretending they ran under today's rules. Third-party registries
|
|
35
|
+
* may carry other records; only this exact combination admits an entry to the official registry. */
|
|
36
|
+
export const OFFICIAL_SECURITY_REVIEW_POLICY = "intentic-extension-security-v1";
|
|
37
|
+
export const OFFICIAL_SECURITY_REVIEWER = "intentic-agent-gate";
|
|
38
|
+
export const OFFICIAL_DETERMINISTIC_SCAN_POLICY = "intentic-extension-deterministic-v1";
|
|
39
|
+
export const OFFICIAL_DETERMINISTIC_SCANNER = "trivy";
|
|
40
|
+
export const OFFICIAL_DETERMINISTIC_SCANNER_VERSION = "0.72.0";
|
|
41
|
+
|
|
42
|
+
const FULL_SHA = /^[0-9a-f]{40}$/;
|
|
43
|
+
|
|
44
|
+
export const RegistrySecurityReviewSchema = z.object({
|
|
45
|
+
sha: z.string().regex(FULL_SHA, "must be a full lowercase commit sha"),
|
|
46
|
+
url: z.string().min(1),
|
|
47
|
+
path: z.string().min(1).optional(),
|
|
48
|
+
policy: z.string().min(1),
|
|
49
|
+
reviewer: z.string().min(1),
|
|
50
|
+
reviewedAt: z.iso.datetime(),
|
|
51
|
+
runId: z.string().min(1),
|
|
52
|
+
deterministic: z.object({
|
|
53
|
+
policy: z.string().min(1),
|
|
54
|
+
scanner: z.string().min(1),
|
|
55
|
+
version: z.string().min(1),
|
|
56
|
+
runId: z.string().min(1),
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
export type RegistrySecurityReview = z.infer<typeof RegistrySecurityReviewSchema>;
|
|
60
|
+
|
|
31
61
|
/* WHAT A LISTING CLAIMS, and precisely what it doesn't.
|
|
32
62
|
*
|
|
33
|
-
* - `listed`
|
|
34
|
-
*
|
|
35
|
-
* - `verified` a human read the source at that sha. Sorted first
|
|
63
|
+
* - `listed` no HUMAN source review is claimed. In the official registry the separate securityReview must
|
|
64
|
+
* still show that both automated checks passed; third-party registries remain their own boundary.
|
|
65
|
+
* - `verified` both automated checks passed and a human also read the source at that sha. Sorted first and badged.
|
|
36
66
|
* - `blocked` known-malicious or known-broken. It STAYS in the file with a reason rather than being deleted:
|
|
37
67
|
* removing the row hides it from people browsing and tells the people who already installed it
|
|
38
68
|
* nothing, which is backwards — they are the ones at risk.
|
|
@@ -42,49 +72,125 @@ export const REGISTRY_TOPIC = "intentic-extension";
|
|
|
42
72
|
export const RegistryTrustSchema = z.enum(["verified", "listed", "blocked"]);
|
|
43
73
|
export type RegistryTrust = z.infer<typeof RegistryTrustSchema>;
|
|
44
74
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// trust state with no stated reason is an opinion the reader can't weigh.
|
|
54
|
-
trustReason: z.string().optional(),
|
|
55
|
-
category: z.string().optional(),
|
|
56
|
-
/* The mark the row is drawn with, copied off the extension's manifest by the scanner exactly like the
|
|
57
|
-
* description and the version — the same two tiers the manifest declares (a simple-icons slug, then a name
|
|
58
|
-
* from the app's icon set), and neither is required.
|
|
59
|
-
*
|
|
60
|
-
* It rides the CURATED file rather than the generated one, which looks wrong for a derived value until you
|
|
61
|
-
* ask what a registry is for: a listing is what a human decided to publish, and the mark is part of how it
|
|
62
|
-
* presents itself, so it belongs in the row a reviewer reads and can strike out. The generated file holds
|
|
63
|
-
* only what a bot re-reads nightly and nobody reviews. It also has to be here to be of any use at all —
|
|
64
|
-
* this is what the gallery and the in-app browse list render, and neither of them has the manifest: the
|
|
65
|
-
* whole point of the row is that the code has NOT been cloned yet. */
|
|
66
|
-
logo: z.string().optional(),
|
|
67
|
-
icon: z.string().optional(),
|
|
68
|
-
homepage: z.url().optional(),
|
|
69
|
-
source: z.unknown(),
|
|
70
|
-
});
|
|
75
|
+
/* WHAT A LISTING COSTS. `free` is the default and the whole story for most rows. `premium` opts the listing
|
|
76
|
+
* into the creator pool: installing it requires an intentic membership and donates a published number of the
|
|
77
|
+
* member's credits to the publisher (once, deduped monthly — an update in a later month donates again), and
|
|
78
|
+
* both surfaces badge it so the price is visible before the click. No usage is ever metered or reported for
|
|
79
|
+
* this; the deliberate act of installing is the whole signal. On a third-party registry the field still
|
|
80
|
+
* parses but means nothing — the pool only pays listings the platform's members actually install. */
|
|
81
|
+
export const RegistryTierSchema = z.enum(["free", "premium"]);
|
|
82
|
+
export type RegistryTier = z.infer<typeof RegistryTierSchema>;
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
const RegistryFileEntrySchema = z
|
|
85
|
+
.object({
|
|
86
|
+
name: z.string(),
|
|
87
|
+
description: z.string().optional(),
|
|
88
|
+
version: z.string().optional(),
|
|
89
|
+
// "extension" installs as the sha-pinned `extension` capability; absent/"plugin" is a Claude Code plugin.
|
|
90
|
+
kind: z.enum(["plugin", "extension"]).optional(),
|
|
91
|
+
trust: RegistryTrustSchema.optional(),
|
|
92
|
+
// Why it is blocked, or what was checked to verify it — shown verbatim wherever the badge is, because a
|
|
93
|
+
// trust state with no stated reason is an opinion the reader can't weigh.
|
|
94
|
+
trustReason: z.string().optional(),
|
|
95
|
+
// Both automated checks' evidence, bound to the complete source identity below. It is deliberately
|
|
96
|
+
// distinct from `trust: verified`: passing admission admits code; verified additionally says a human
|
|
97
|
+
// read it. Keeping those claims apart stops automated verdicts being presented as human review.
|
|
98
|
+
securityReview: RegistrySecurityReviewSchema.optional(),
|
|
99
|
+
// This listing's pinned commit fixes a security problem in earlier commits — the fast lane: an installed
|
|
100
|
+
// sandbox promotes its "update available" badge from ambient to attention-demanding, because there the OLD
|
|
101
|
+
// version is the dangerous one. Asserted by the pull request like trust, and worth exactly that review.
|
|
102
|
+
securityFix: z.boolean().optional(),
|
|
103
|
+
tier: RegistryTierSchema.optional(),
|
|
104
|
+
category: z.string().optional(),
|
|
105
|
+
/* The mark the row is drawn with, copied off the extension's manifest exactly like the description and
|
|
106
|
+
* the version — the same three tiers the manifest declares (the author's own inline drawing, then a
|
|
107
|
+
* simple-icons slug, then a name from the app's icon set), and none is required.
|
|
108
|
+
*
|
|
109
|
+
* It rides the CURATED file rather than the generated one, which looks wrong for a derived value until you
|
|
110
|
+
* ask what a registry is for: a listing is what a human decided to publish, and the mark is part of how it
|
|
111
|
+
* presents itself, so it belongs in the row a reviewer reads and can strike out. The generated file holds
|
|
112
|
+
* only what a bot re-reads nightly and nobody reviews. It also has to be here to be of any use at all —
|
|
113
|
+
* this is what the gallery and the in-app browse list render, and neither of them has the manifest: the
|
|
114
|
+
* whole point of the row is that the code has NOT been cloned yet.
|
|
115
|
+
*
|
|
116
|
+
* `art` being reviewable is the reason it is capped and kept as SVG text: it is the one field here that
|
|
117
|
+
* is a picture, and a picture nobody can read in the diff is a picture nobody reviewed. Four kilobytes
|
|
118
|
+
* of readable shapes is a thing a reviewer can strike out; a base64 blob is not. */
|
|
119
|
+
art: z.string().max(4096).optional(),
|
|
120
|
+
logo: z.string().optional(),
|
|
121
|
+
icon: z.string().optional(),
|
|
122
|
+
homepage: z.url().optional(),
|
|
123
|
+
source: z.unknown(),
|
|
124
|
+
})
|
|
125
|
+
.superRefine((entry, ctx) => {
|
|
126
|
+
const trust = entry.trust ?? "listed";
|
|
127
|
+
if (trust === "blocked" && (entry.trustReason === undefined || entry.trustReason.trim() === "")) {
|
|
128
|
+
ctx.addIssue({ code: "custom", path: ["trustReason"], message: "a blocked entry must say why" });
|
|
129
|
+
}
|
|
130
|
+
if (trust === "verified" && entry.securityReview === undefined) {
|
|
131
|
+
ctx.addIssue({ code: "custom", path: ["securityReview"], message: "a verified entry must carry its security review" });
|
|
132
|
+
}
|
|
133
|
+
if (entry.securityReview === undefined) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const install = resolveSource(entry.source, "", undefined);
|
|
137
|
+
if (install?.ref !== entry.securityReview.sha || install?.url !== entry.securityReview.url || install.path !== entry.securityReview.path) {
|
|
138
|
+
ctx.addIssue({
|
|
139
|
+
code: "custom",
|
|
140
|
+
path: ["securityReview"],
|
|
141
|
+
message: "must equal the exact repository, commit and subdirectory named by source",
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
export const RegistryFileSchema = z
|
|
147
|
+
.object({
|
|
148
|
+
name: z.string(),
|
|
149
|
+
metadata: z.object({ pluginRoot: z.string().optional() }).optional(),
|
|
150
|
+
plugins: z.array(RegistryFileEntrySchema),
|
|
151
|
+
})
|
|
152
|
+
.superRefine((file, ctx) => {
|
|
153
|
+
const seen = new Set<string>();
|
|
154
|
+
for (let at = 0; at < file.plugins.length; at += 1) {
|
|
155
|
+
const name = file.plugins[at]?.name;
|
|
156
|
+
if (name !== undefined && seen.has(name)) {
|
|
157
|
+
ctx.addIssue({ code: "custom", path: ["plugins", at, "name"], message: "entry names must be unique" });
|
|
158
|
+
}
|
|
159
|
+
if (name !== undefined) {
|
|
160
|
+
seen.add(name);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
77
164
|
export type RegistryFile = z.infer<typeof RegistryFileSchema>;
|
|
78
165
|
|
|
79
166
|
/* Facts read off the source host, keyed by the curated entry's name. Deliberately NOT the upstream head sha:
|
|
80
167
|
* the approved sha is the one that runs, updating is a pull request, and a file that advertised "there's a
|
|
81
168
|
* newer commit over there" would be inviting a click that skips the review the whole model rests on. */
|
|
169
|
+
/* What the scan re-derived COLD at the listing's pinned sha — the same questions the daemon's readiness check
|
|
170
|
+
* answers for an author before publishing, asked again by a stranger with nothing but the pointer. That
|
|
171
|
+
* re-derivation is the whole value: an author's own checks describe the directory they ran them in, and these
|
|
172
|
+
* describe what an installer actually gets. `sha` binds the answers to the commit they were read from, so a
|
|
173
|
+
* listing repointed since the last scan renders no stale verdicts — the join drops checks whose sha no longer
|
|
174
|
+
* matches rather than letting yesterday's answer describe today's pointer. */
|
|
175
|
+
const RegistryChecksSchema = z.object({
|
|
176
|
+
sha: z.string(),
|
|
177
|
+
// "ok", or the reason it is not, verbatim — a verdict with no stated reason is an opinion.
|
|
178
|
+
manifest: z.string(),
|
|
179
|
+
// "ok" / "none" (no UI bundle) / the reason the bundle cannot load where it is installed.
|
|
180
|
+
bundle: z.string(),
|
|
181
|
+
// The engines range the manifest declares at that sha. A raw fact rather than a verdict on purpose: the
|
|
182
|
+
// scan does not know which app version any particular reader runs, but every consumer of this file does.
|
|
183
|
+
engines: z.string().optional(),
|
|
184
|
+
});
|
|
185
|
+
export type RegistryChecks = z.infer<typeof RegistryChecksSchema>;
|
|
186
|
+
|
|
82
187
|
const RegistryFactsEntrySchema = z.object({
|
|
83
188
|
name: z.string(),
|
|
84
189
|
stars: z.number().int().nonnegative().optional(),
|
|
85
190
|
// ISO-8601, last push to the source repo's default branch — the tiebreaker that does the real work while
|
|
86
191
|
// every listing still has single-digit stars.
|
|
87
192
|
pushedAt: z.string().optional(),
|
|
193
|
+
checks: RegistryChecksSchema.optional(),
|
|
88
194
|
});
|
|
89
195
|
|
|
90
196
|
export const RegistryFactsSchema = z.object({
|
|
@@ -102,8 +208,17 @@ export const RegistryEntrySchema = z.object({
|
|
|
102
208
|
kind: z.enum(["plugin", "extension"]),
|
|
103
209
|
trust: RegistryTrustSchema,
|
|
104
210
|
trustReason: z.string().optional(),
|
|
211
|
+
securityReview: RegistrySecurityReviewSchema.optional(),
|
|
212
|
+
// False only for an executable row the official registry has not put through its current checks (and for a
|
|
213
|
+
// blocked row). The row remains visible so a reader gets a reason instead of a mysteriously missing entry,
|
|
214
|
+
// but every install/update surface refuses to act on it. Third-party registries remain their own trust
|
|
215
|
+
// boundary and resolve their non-blocked rows as admitted without adopting intentic's policy.
|
|
216
|
+
admitted: z.boolean(),
|
|
217
|
+
securityFix: z.boolean().optional(),
|
|
218
|
+
tier: RegistryTierSchema,
|
|
105
219
|
category: z.string().optional(),
|
|
106
220
|
// The mark, as the gallery and the app's browse list draw it — see the curated file's fields above.
|
|
221
|
+
art: z.string().optional(),
|
|
107
222
|
logo: z.string().optional(),
|
|
108
223
|
icon: z.string().optional(),
|
|
109
224
|
homepage: z.string().optional(),
|
|
@@ -112,30 +227,77 @@ export const RegistryEntrySchema = z.object({
|
|
|
112
227
|
install: RegistryInstallSchema.optional(),
|
|
113
228
|
stars: z.number().int().nonnegative().optional(),
|
|
114
229
|
pushedAt: z.string().optional(),
|
|
230
|
+
checks: RegistryChecksSchema.optional(),
|
|
115
231
|
});
|
|
116
232
|
export type RegistryEntry = z.infer<typeof RegistryEntrySchema>;
|
|
117
233
|
|
|
234
|
+
const normalizedRegistryUrl = (value: string): string | undefined => {
|
|
235
|
+
const scp = value.includes("://") ? null : /^(?:[^@]+@)?([^:]+):\/?(.+)$/u.exec(value);
|
|
236
|
+
if (scp !== null) {
|
|
237
|
+
return `${scp[1]?.toLowerCase()}/${scp[2]
|
|
238
|
+
?.replace(/\.git\/?$/u, "")
|
|
239
|
+
.replace(/\/$/u, "")
|
|
240
|
+
.toLowerCase()}`;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
const url = new URL(value);
|
|
244
|
+
const path = url.pathname
|
|
245
|
+
.replace(/\.git\/?$/u, "")
|
|
246
|
+
.replace(/\/$/u, "")
|
|
247
|
+
.toLowerCase();
|
|
248
|
+
return `${url.host.toLowerCase()}${path}`;
|
|
249
|
+
} catch {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
export const isOfficialRegistryUrl = (value: string): boolean => normalizedRegistryUrl(value) === normalizedRegistryUrl(OFFICIAL_REGISTRY_URL);
|
|
255
|
+
|
|
256
|
+
export const isCurrentSecurityReview = (review: RegistrySecurityReview | undefined, install: RegistryInstall | undefined): boolean =>
|
|
257
|
+
review !== undefined &&
|
|
258
|
+
review.sha === install?.ref &&
|
|
259
|
+
review.url === install.url &&
|
|
260
|
+
review.path === install.path &&
|
|
261
|
+
review.policy === OFFICIAL_SECURITY_REVIEW_POLICY &&
|
|
262
|
+
review.reviewer === OFFICIAL_SECURITY_REVIEWER &&
|
|
263
|
+
review.deterministic.policy === OFFICIAL_DETERMINISTIC_SCAN_POLICY &&
|
|
264
|
+
review.deterministic.scanner === OFFICIAL_DETERMINISTIC_SCANNER &&
|
|
265
|
+
review.deterministic.version === OFFICIAL_DETERMINISTIC_SCANNER_VERSION;
|
|
266
|
+
|
|
118
267
|
/* Join the curated file to the generated facts. `facts` is undefined for any registry that runs no scanner,
|
|
119
268
|
* which is most of them — a private registry of six internal extensions wants the pointers and nothing else. */
|
|
120
269
|
export const resolveRegistry = (file: RegistryFile, facts: RegistryFacts | undefined, registryUrl: string): RegistryEntry[] => {
|
|
121
270
|
const byName = new Map(facts?.entries.map((entry) => [entry.name, entry]) ?? []);
|
|
271
|
+
const official = isOfficialRegistryUrl(registryUrl);
|
|
122
272
|
return file.plugins.map((plugin) => {
|
|
123
273
|
const upstream = byName.get(plugin.name);
|
|
124
274
|
const install: RegistryInstall | undefined = resolveSource(plugin.source, registryUrl, file.metadata?.pluginRoot);
|
|
275
|
+
const kind = plugin.kind ?? "plugin";
|
|
276
|
+
const trust = plugin.trust ?? "listed";
|
|
277
|
+
const admitted = trust !== "blocked" && (kind !== "extension" || !official || isCurrentSecurityReview(plugin.securityReview, install));
|
|
125
278
|
return {
|
|
126
279
|
name: plugin.name,
|
|
127
|
-
kind
|
|
128
|
-
trust
|
|
280
|
+
kind,
|
|
281
|
+
trust,
|
|
282
|
+
admitted,
|
|
283
|
+
// Absent ⇒ free, so a registry that has never heard of the pool keeps meaning what it always did.
|
|
284
|
+
tier: plugin.tier ?? "free",
|
|
129
285
|
...(plugin.description !== undefined ? { description: plugin.description } : {}),
|
|
130
286
|
...(plugin.version !== undefined ? { version: plugin.version } : {}),
|
|
131
287
|
...(plugin.trustReason !== undefined ? { trustReason: plugin.trustReason } : {}),
|
|
288
|
+
...(plugin.securityReview !== undefined ? { securityReview: plugin.securityReview } : {}),
|
|
289
|
+
...(plugin.securityFix !== undefined ? { securityFix: plugin.securityFix } : {}),
|
|
132
290
|
...(plugin.category !== undefined ? { category: plugin.category } : {}),
|
|
291
|
+
...(plugin.art !== undefined ? { art: plugin.art } : {}),
|
|
133
292
|
...(plugin.logo !== undefined ? { logo: plugin.logo } : {}),
|
|
134
293
|
...(plugin.icon !== undefined ? { icon: plugin.icon } : {}),
|
|
135
294
|
...(plugin.homepage !== undefined ? { homepage: plugin.homepage } : {}),
|
|
136
295
|
...(install !== undefined ? { install } : {}),
|
|
137
296
|
...(upstream?.stars !== undefined ? { stars: upstream.stars } : {}),
|
|
138
297
|
...(upstream?.pushedAt !== undefined ? { pushedAt: upstream.pushedAt } : {}),
|
|
298
|
+
// Only when derived from the sha this row still points at — a repointed listing renders no checks
|
|
299
|
+
// until the next scan, which is the honest gap rather than yesterday's verdict on today's pointer.
|
|
300
|
+
...(upstream?.checks !== undefined && upstream.checks.sha === install?.ref ? { checks: upstream.checks } : {}),
|
|
139
301
|
};
|
|
140
302
|
});
|
|
141
303
|
};
|
package/src/source.test.ts
CHANGED
|
@@ -22,7 +22,9 @@ describe(`resolveSource`, () => {
|
|
|
22
22
|
url: `https://gitlab.com/acme/x.git`,
|
|
23
23
|
ref: SHA,
|
|
24
24
|
});
|
|
25
|
-
expect(
|
|
25
|
+
expect(
|
|
26
|
+
resolveSource({ source: `git-subdir`, url: `https://github.com/acme/tools.git`, path: `ext/a`, sha: SHA }, REGISTRY, undefined),
|
|
27
|
+
).toEqual({
|
|
26
28
|
url: `https://github.com/acme/tools.git`,
|
|
27
29
|
path: `ext/a`,
|
|
28
30
|
ref: SHA,
|