@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 +21 -0
- package/README.md +91 -0
- package/bundle/index.d.ts +185 -0
- package/bundle/index.js +847 -0
- package/bundle/inner-cli.js +995 -0
- package/bundle/scan-cli.js +808 -0
- package/package.json +66 -0
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 };
|