@inner-security/scan 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Inner Security
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # @inner-security/scan — Inner CLI
2
+
3
+ A **thin client** of Inner's Verdict API:
4
+
5
+ - **`npx @inner-security/scan`** — instant dependency scan. Reads your
6
+ manifest/lockfile, calls the Verdict API, and prints the protected tally
7
+ (`N packages scanned, K allowed, M blocked`).
8
+ - **`inner init`** — auto-configures the **6 package managers**
9
+ (npm / pnpm / yarn / pip / poetry / uv) to resolve through the Inner registry proxy.
10
+
11
+ All detection, scoring, and auth live **server-side**. This package ships no
12
+ detection logic — it only consumes the Verdict API. Real usage is **gated by an
13
+ Inner org API key** (the Datadog model): without a key the scan resolves against
14
+ the public service surface only.
15
+
16
+ ## Install / run
17
+
18
+ ```bash
19
+ # Instant scan (cwd) — no install needed
20
+ npx @inner-security/scan
21
+ npx @inner-security/scan --dir ./my-project --json
22
+
23
+ # Or install globally for the `inner` umbrella CLI
24
+ npm i -g @inner-security/scan
25
+ inner scan
26
+ inner init
27
+ inner init --proxy https://proxy.example.com --managers npm,pnpm,pip
28
+ ```
29
+
30
+ Bins exposed: `scan` / `inner-scan` (the scan path) and `inner` (umbrella CLI
31
+ with `scan` + `init`). Requires **Node >= 20**.
32
+
33
+ ## API key
34
+
35
+ Real verdicts are gated by your Inner org API key. Provide it via env:
36
+
37
+ ```bash
38
+ export INNER_API_KEY=ik_...
39
+ npx @inner-security/scan
40
+ ```
41
+
42
+ ## Configurable endpoints
43
+
44
+ Both hosts are env/flag-configurable and **default to dev values today**. Once
45
+ Inner's production hosts are live (PROD-WIRING 3/4/12) these defaults point at
46
+ prod; until then, set them explicitly for real verdicts:
47
+
48
+ | What | Flag | Env | Default (dev) |
49
+ |------|------|-----|---------------|
50
+ | Verdict API base | `--api-url` | `INNER_API_URL` | `http://localhost:8787` (the local `verdict-api-mock`) |
51
+ | Registry proxy base | `--proxy` (init) | `INNER_PROXY_URL` | `https://proxy.inner.dev` (placeholder) |
52
+ | Org API key | — | `INNER_API_KEY` | (none) |
53
+
54
+ > The dev defaults are intentional: the published client is name-claimed now and
55
+ > flips to prod hosts once E1's Verdict API + proxy are wired live.
56
+
57
+ ## Verdict resolution
58
+
59
+ - Items are sent to `POST /v1/verdict` (`VerdictRequest`).
60
+ - The response carries `VerdictEnvelope | PendingResponse`.
61
+ - `PENDING` items are polled at `GET /v1/verdict/{job_id}` with backoff.
62
+ - Every requested package is bucketed by its envelope `action`
63
+ (`ALLOW` / `BLOCK` / `REVIEW`); unresolved → `unknown`.
64
+
65
+ ## Fail-open cache
66
+
67
+ Resolved verdicts are written to `.inner-cache/` (last-known-good). If the API is
68
+ unreachable, the scan falls back to that cache: **cached-ALLOW packages still
69
+ resolve**, everything else surfaces as `unknown`. The run is marked *degraded*
70
+ rather than failing outright. Pass `--no-cache` to disable.
71
+
72
+ ## Exit codes
73
+
74
+ `0` clean · `1` one or more BLOCKED packages · `2` usage/error.
75
+
76
+ ## Package-manager config written by `inner init`
77
+
78
+ | Manager | File | Key |
79
+ |---------|------|-----|
80
+ | npm | `.npmrc` | `registry=<proxy>/npm/` |
81
+ | pnpm | `.npmrc` | `registry=<proxy>/npm/` |
82
+ | yarn | `.yarnrc.yml` | `npmRegistryServer: "<proxy>/npm/"` |
83
+ | pip | `pip.conf` | `[global] index-url = <proxy>/pypi/simple/` |
84
+ | poetry | `poetry.toml` | `[[tool.poetry.source]] url = <proxy>/pypi/simple/` |
85
+ | uv | `uv.toml` | `[[index]] url = <proxy>/pypi/simple/` |
86
+
87
+ `inner init` is idempotent and preserves unrelated config lines.
88
+
89
+ ## License
90
+
91
+ MIT © 2026 Inner Security. See [LICENSE](./LICENSE).
@@ -0,0 +1,185 @@
1
+ import { VerdictEnvelope, VerdictRequestItem, Ecosystem } from '@inner/contracts';
2
+ import { ScanClientResult, ResolvedItem } from '@inner/verdict-client';
3
+ export { DEFAULT_API_URL, ResolvedItem, ScanClientResult } from '@inner/verdict-client';
4
+
5
+ /**
6
+ * Default registry proxy base. PLACEHOLDER — the real Inner proxy host (owned by
7
+ * E1) must be wired in via INNER_PROXY_URL or `inner init --proxy <url>`.
8
+ */
9
+ declare const DEFAULT_PROXY_URL = "https://proxy.inner.dev";
10
+ interface ResolvedConfig {
11
+ /** Verdict API base URL (no trailing slash). */
12
+ apiUrl: string;
13
+ /** Registry proxy base URL (no trailing slash). */
14
+ proxyUrl: string;
15
+ /** true when apiUrl came from a default (prod URL not wired in). */
16
+ apiUrlIsDefault: boolean;
17
+ /** true when proxyUrl came from a default (prod proxy not wired in). */
18
+ proxyUrlIsDefault: boolean;
19
+ }
20
+ /**
21
+ * Resolve the Verdict API base URL. Precedence: explicit flag value >
22
+ * INNER_API_URL env > DEFAULT_API_URL.
23
+ */
24
+ declare function resolveApiUrl(flagValue?: string): {
25
+ url: string;
26
+ isDefault: boolean;
27
+ };
28
+ /**
29
+ * Resolve the registry proxy base URL. Precedence: explicit flag value >
30
+ * INNER_PROXY_URL env > DEFAULT_PROXY_URL.
31
+ */
32
+ declare function resolveProxyUrl(flagValue?: string): {
33
+ url: string;
34
+ isDefault: boolean;
35
+ };
36
+ declare function resolveConfig(opts?: {
37
+ apiUrl?: string;
38
+ proxyUrl?: string;
39
+ }): ResolvedConfig;
40
+
41
+ declare const CACHE_DIR_NAME = ".inner-cache";
42
+ /**
43
+ * Max age (ms) a cached entry is trusted as last-known-good when the envelope
44
+ * carries no usable `expires_at`. Fail-open is a stop-gap for a transient API
45
+ * outage, not a license to trust an arbitrarily old verdict. 7 days.
46
+ */
47
+ declare const CACHE_MAX_AGE_MS: number;
48
+ declare function cacheKey(item: {
49
+ ecosystem: string;
50
+ name: string;
51
+ version: string;
52
+ }): string;
53
+ interface CacheEntry {
54
+ key: string;
55
+ cached_at: string;
56
+ envelope: VerdictEnvelope;
57
+ }
58
+ declare class VerdictCache {
59
+ readonly dir: string;
60
+ constructor(rootDir?: string);
61
+ private pathFor;
62
+ /** Persist a resolved envelope as last-known-good. */
63
+ put(item: VerdictRequestItem, envelope: VerdictEnvelope): void;
64
+ /**
65
+ * Read a last-known-good envelope that is safe to TRUST for resolution, or
66
+ * undefined on miss/parse error/untrusted entry.
67
+ *
68
+ * The cache is fail-OPEN: only a cached ALLOW that has not gone stale is a
69
+ * usable verdict. A cached BLOCK/REVIEW (or anything non-ALLOW), or an
70
+ * expired entry, is treated as a miss so the caller surfaces UNKNOWN and the
71
+ * operator decides — never a silent allow of a now-known-bad package.
72
+ */
73
+ get(item: {
74
+ ecosystem: string;
75
+ name: string;
76
+ version: string;
77
+ }, now?: Date): VerdictEnvelope | undefined;
78
+ }
79
+
80
+ interface ManifestScanResult {
81
+ items: VerdictRequestItem[];
82
+ /** Files we actually read (for the scan summary / debugging). */
83
+ sources: string[];
84
+ /** Ecosystems detected in the project root. */
85
+ ecosystems: Ecosystem[];
86
+ /**
87
+ * Lines/entries the parser could not turn into a concrete (name, version) and
88
+ * skipped rather than guess (range-only specs, "*"/"latest", git/url deps, …).
89
+ * Surfaced so a skipped package is never silently treated as unprotected.
90
+ */
91
+ warnings: string[];
92
+ /** Count of skipped/unparseable entries (== warnings.length). */
93
+ skipped: number;
94
+ }
95
+ /**
96
+ * Scan a project root. For each ecosystem we prefer the most exact source
97
+ * available (lockfile > manifest) and stop at the first that yields items.
98
+ */
99
+ declare function scanManifests(root?: string): ManifestScanResult;
100
+
101
+ interface ClientOptions {
102
+ apiUrl: string;
103
+ /** Per-request timeout in ms. */
104
+ timeoutMs?: number;
105
+ /** Max poll attempts for a single PENDING job. */
106
+ maxPollAttempts?: number;
107
+ /** Project root used to locate / write `.inner-cache/`. */
108
+ cacheRoot?: string;
109
+ /** Disable disk cache entirely (used in tests). */
110
+ noCache?: boolean;
111
+ }
112
+ declare class VerdictClient {
113
+ private readonly client;
114
+ constructor(opts: ClientOptions);
115
+ /**
116
+ * Resolve a batch of items. Returns one ResolvedItem per requested item.
117
+ * On total API failure, falls back to the fail-open cache for every item.
118
+ */
119
+ resolve(items: VerdictRequestItem[]): Promise<ScanClientResult>;
120
+ }
121
+
122
+ interface ScanTally {
123
+ scanned: number;
124
+ allowed: number;
125
+ blocked: number;
126
+ review: number;
127
+ unknown: number;
128
+ fromCache: number;
129
+ }
130
+ interface ScanResult {
131
+ tally: ScanTally;
132
+ resolved: ResolvedItem[];
133
+ sources: string[];
134
+ ecosystems: string[];
135
+ degraded: boolean;
136
+ apiUrl: string;
137
+ apiUrlIsDefault: boolean;
138
+ /** Manifest entries the parser couldn't resolve to (name, version) and skipped. */
139
+ skipped: number;
140
+ /** One human-readable line per skipped entry (why it was skipped). */
141
+ warnings: string[];
142
+ }
143
+ declare function aggregate(resolved: ResolvedItem[]): ScanTally;
144
+ interface ScanOptions {
145
+ root?: string;
146
+ apiUrl?: string;
147
+ noCache?: boolean;
148
+ /** Pre-computed manifest result (used in tests to bypass disk). */
149
+ manifest?: ManifestScanResult;
150
+ }
151
+ declare function scan(opts?: ScanOptions): Promise<ScanResult>;
152
+ /** Human-readable one-line tally, e.g. the acceptance string. */
153
+ declare function formatTally(t: ScanTally): string;
154
+
155
+ type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'pip' | 'poetry' | 'uv';
156
+ declare const ALL_PACKAGE_MANAGERS: PackageManager[];
157
+ interface ConfiguredManager {
158
+ manager: PackageManager;
159
+ file: string;
160
+ registry: string;
161
+ /** 'created' | 'updated' (existing file edited). */
162
+ outcome: 'created' | 'updated';
163
+ }
164
+ interface InitOptions {
165
+ root?: string;
166
+ proxyUrl?: string;
167
+ /** Only configure these managers (default: all 6). */
168
+ managers?: PackageManager[];
169
+ }
170
+ interface InitResult {
171
+ configured: ConfiguredManager[];
172
+ proxyUrl: string;
173
+ proxyUrlIsDefault: boolean;
174
+ }
175
+ declare function init(opts?: InitOptions): InitResult;
176
+
177
+ interface ParsedArgs {
178
+ _: string[];
179
+ flags: Record<string, string | boolean>;
180
+ }
181
+ declare function parseArgs(argv: string[]): ParsedArgs;
182
+ declare function flagString(flags: Record<string, string | boolean>, key: string): string | undefined;
183
+ declare function flagBool(flags: Record<string, string | boolean>, key: string): boolean;
184
+
185
+ export { ALL_PACKAGE_MANAGERS, CACHE_DIR_NAME, CACHE_MAX_AGE_MS, type CacheEntry, type ClientOptions, type ConfiguredManager, DEFAULT_PROXY_URL, type InitOptions, type InitResult, type ManifestScanResult, type PackageManager, type ParsedArgs, type ResolvedConfig, type ScanOptions, type ScanResult, type ScanTally, VerdictCache, VerdictClient, aggregate, cacheKey, flagBool, flagString, formatTally, init, parseArgs, resolveApiUrl, resolveConfig, resolveProxyUrl, scan, scanManifests };