@shieldly/cli 1.0.1 → 1.0.3
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 +33 -3
- package/dist/cli.cjs +75 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,10 +10,30 @@ any terminal, shell script, or CI/CD pipeline. Powered by
|
|
|
10
10
|
npm install -g @shieldly/cli
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
## Try it free — no account needed
|
|
14
|
+
|
|
15
|
+
Both `analyze-iam` and `analyze-cf` run in demo mode without an API key
|
|
16
|
+
(5 free analyses, no signup required):
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
shieldly analyze-iam policy.json
|
|
20
|
+
shieldly analyze-cf template.yaml
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For higher limits and analysis history,
|
|
24
|
+
[create a free account](https://www.shieldly.io/app/api) and set your key:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export SHIELDLY_API_KEY=sk_live_...
|
|
28
|
+
shieldly analyze-cf template.yaml
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Upgrade to a paid plan for higher daily limits, team access, and priority AI.
|
|
32
|
+
|
|
13
33
|
## Quick start
|
|
14
34
|
|
|
15
35
|
```bash
|
|
16
|
-
#
|
|
36
|
+
# Set your API key (get one free at https://www.shieldly.io/app/api)
|
|
17
37
|
export SHIELDLY_API_KEY=sk_live_...
|
|
18
38
|
|
|
19
39
|
# Analyze an IAM policy
|
|
@@ -26,8 +46,18 @@ shieldly analyze-cf template.yaml
|
|
|
26
46
|
shieldly api-keys list
|
|
27
47
|
```
|
|
28
48
|
|
|
29
|
-
|
|
30
|
-
|
|
49
|
+
## Free tier
|
|
50
|
+
|
|
51
|
+
| Mode | Limit | Requires |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| Demo (no key) | 5 analyses total | No account |
|
|
54
|
+
| Free account | 20 units/day | Free sign-up |
|
|
55
|
+
| Builder | 150 units/day | Paid plan |
|
|
56
|
+
| Pro | 300 units/day | Paid plan |
|
|
57
|
+
| Team | 600 units/day | Paid plan |
|
|
58
|
+
|
|
59
|
+
Analysis units depend on input size — a small IAM policy costs 1 unit;
|
|
60
|
+
large CloudFormation templates cost more. [Compare plans →](https://www.shieldly.io/#pricing)
|
|
31
61
|
|
|
32
62
|
## Commands
|
|
33
63
|
|
package/dist/cli.cjs
CHANGED
|
@@ -1,27 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
`)
|
|
4
|
-
`),{...i.result,unitInfo:i.unitInfo};if(i.status==="failed")throw process.stderr.write(`
|
|
5
|
-
`),new Error(i.error||"Analysis failed")}throw process.stderr.write(`
|
|
6
|
-
`),new Error("Analysis timed out after polling")}async function v(e,o){let t=L(),n=await fetch(`${t}${e}`,{headers:{Authorization:`Bearer ${o}`,"User-Agent":w}});if(!n.ok){let s=await n.json().catch(()=>({}));throw new Error(s.error||`API error ${n.status}`)}return n.json()}async function M(e,o,t){let n=L(),s=await fetch(`${n}${e}`,{method:"DELETE",headers:{"Content-Type":"application/json","User-Agent":w,Authorization:`Bearer ${t}`},body:JSON.stringify(o)});if(!s.ok){let r=await s.json().catch(()=>({}));throw new Error(r.error||`API error ${s.status}`)}return s.json()}function x(e){return(0,g.existsSync)(e)||(console.error(`Error: File not found: ${e}`),process.exit(1)),(0,g.readFileSync)(e,"utf8")}var K={CRITICAL:"\x1B[31m",HIGH:"\x1B[33m",MEDIUM:"\x1B[36m",LOW:"\x1B[32m",INFO:"\x1B[90m"},y="\x1B[0m",A="\x1B[1m",_="\x1B[2m",Y="\x1B[36m";function P(e,o){if(o==="json"){console.log(JSON.stringify(e,null,2));return}let{score:t,riskLevel:n,findings:s=[],cached:r}=e;if(console.log(""),console.log(`${A}AI-Powered Security Analysis \u2014 Shieldly${y}`),console.log(`${_}${"\u2500".repeat(50)}${y}`),console.log(` ${A}Security Score:${y} ${ne(t)}${t}/100${y}`),console.log(` ${A}Risk Level:${y} ${le(n)}${n}${y}`),r&&console.log(` ${_}(cached result)${y}`),console.log(""),s.length===0)console.log(` ${Y}[PASS] No findings${y}`);else{console.log(`${A}Findings (${s.length}):${y}`);for(let a of s){let l=K[(a.severity||"").toUpperCase()]||"";console.log(`
|
|
7
|
-
${l}[${a.severity}]${y} ${A}${a.title}${y}`),a.description&&console.log(` ${_}${a.description}${y}`),a.remediation&&console.log(` ${Y}Fix:${y} ${a.remediation}`)}}console.log("")}function ne(e){return e>=80?"\x1B[32m":e>=50?"\x1B[33m":"\x1B[31m"}function le(e){let o=(e||"").toUpperCase();return K[o]||""}var re=`
|
|
8
|
-
Usage: shieldly analyze-cf <template-file> [options]
|
|
2
|
+
var ne=Object.create;var G=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var ie=Object.getOwnPropertyNames;var le=Object.getPrototypeOf,ae=Object.prototype.hasOwnProperty;var ce=(e,s,o,n)=>{if(s&&typeof s=="object"||typeof s=="function")for(let t of ie(s))!ae.call(e,t)&&t!==o&&G(e,t,{get:()=>s[t],enumerable:!(n=re(s,t))||n.enumerable});return e};var W=(e,s,o)=>(o=e!=null?ne(le(e)):{},ce(s||!e||!e.__esModule?G(o,"default",{value:e,enumerable:!0}):o,e));var k=require("node:fs"),Y=require("node:path");var $=require("node:fs"),B=require("node:os"),z=require("node:path"),O="Shieldly-CLI/1.0.3",U=(0,z.join)((0,B.homedir)(),".shieldly","config.json"),pe="https://api.shieldly.io",ye="https://www.shieldly.io",H=5;function K(){try{return JSON.parse((0,$.readFileSync)(U,"utf8"))}catch{return{}}}function de(e){try{(0,$.mkdirSync)((0,z.dirname)(U),{recursive:!0}),(0,$.writeFileSync)(U,`${JSON.stringify(e,null,2)}
|
|
3
|
+
`)}catch{}}function S(e){return e||(process.env.SHIELDLY_API_KEY?process.env.SHIELDLY_API_KEY:K().apiKey||null)}function q(){let e=K().demoCount;return typeof e=="number"&&e>0?e:0}function _(){return q()>=H}function j(){let e=K();return e.demoCount=q()+1,de(e),Math.max(0,H-e.demoCount)}function D(){return`You've used all ${H} free demo analyses.
|
|
9
4
|
|
|
10
|
-
|
|
5
|
+
Get a free API key for higher limits: https://www.shieldly.io/app/api
|
|
6
|
+
|
|
7
|
+
Then set SHIELDLY_API_KEY or pass --api-key.`}function N(){return(process.env.SHIELDLY_API_URL||pe).replace(/\/$/,"")}function me(){return(process.env.SHIELDLY_WEB_URL||ye).replace(/\/$/,"")}async function C(e,s){let o=me(),n=await fetch(`${o}${e}`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":O},body:JSON.stringify(s)});if(n.status===429)throw new Error("Demo rate limit reached. Get a free API key for higher limits: https://www.shieldly.io/app/api");if(!n.ok){let t=await n.json().catch(()=>({}));throw new Error(t.error||`API error ${n.status}`)}return n.json()}async function A(e,s,o){let n=N(),t=await fetch(`${n}${e}`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":O,Authorization:`Bearer ${o}`},body:JSON.stringify(s)});if(t.status===202){let i=await t.json().catch(()=>({}));if(i.jobId)return ue(i.jobId,o);throw new Error("Analysis queued but no job ID returned \u2014 try again")}if(!t.ok){let i=await t.json().catch(()=>({}));throw new Error(i.error||`API error ${t.status}`)}return t.json()}async function ue(e,s){let o=[2e3,3e3,5e3],n=Date.now(),t=0;for(let i=0;i<180;i++){let l=o[Math.min(i,o.length-1)];await new Promise(r=>setTimeout(r,l));let y=Math.round((Date.now()-n)/1e3);process.stderr.write(`\rAI-Powered analysis in progress\u2026 (${y}s)`);let a;try{a=await T(`/v1/jobs/${encodeURIComponent(e)}`,s),t=0}catch(r){if(++t>=3)throw process.stderr.write(`
|
|
8
|
+
`),r;continue}if(a.status==="complete")return process.stderr.write(`
|
|
9
|
+
`),{...a.result,unitInfo:a.unitInfo};if(a.status==="failed")throw process.stderr.write(`
|
|
10
|
+
`),new Error(a.error||"Analysis failed")}throw process.stderr.write(`
|
|
11
|
+
`),new Error("Analysis timed out after polling")}async function T(e,s){let o=N(),n=await fetch(`${o}${e}`,{headers:{Authorization:`Bearer ${s}`,"User-Agent":O}});if(!n.ok){let t=await n.json().catch(()=>({}));throw new Error(t.error||`API error ${n.status}`)}return n.json()}async function V(e,s,o){let n=N(),t=await fetch(`${n}${e}`,{method:"DELETE",headers:{"Content-Type":"application/json","User-Agent":O,Authorization:`Bearer ${o}`},body:JSON.stringify(s)});if(!t.ok){let i=await t.json().catch(()=>({}));throw new Error(i.error||`API error ${t.status}`)}return t.json()}function R(e){return(0,$.existsSync)(e)||(console.error(`Error: File not found: ${e}`),process.exit(1)),(0,$.statSync)(e).isDirectory()&&(console.error(`Error: ${e} is a directory, not a file.
|
|
12
|
+
For a CDK output directory, use: shieldly analyze-cf ${e}`),process.exit(1)),(0,$.readFileSync)(e,"utf8")}var F={CRITICAL:"\x1B[31m",HIGH:"\x1B[33m",MEDIUM:"\x1B[36m",LOW:"\x1B[32m",INFO:"\x1B[90m"},d="\x1B[0m",E="\x1B[1m",w="\x1B[2m",J="\x1B[36m";function P(e,s){if(s==="json"){console.log(JSON.stringify(e,null,2));return}let{score:o,riskLevel:n,findings:t=[],cached:i,summary:l,positives:y=[],unitInfo:a}=e,r=o==null?"\u2014":`${o}/100`;if(console.log(""),console.log(`${E}AI-Powered Security Analysis \u2014 Shieldly${d}`),console.log(`${w}${"\u2500".repeat(50)}${d}`),console.log(` ${E}Security Score:${d} ${fe(o)}${r}${d}`),console.log(` ${E}Risk Level:${d} ${he(n)}${n||"Unknown"}${d}`),i&&console.log(` ${w}(cached result)${d}`),l&&(console.log(""),console.log(` ${w}${l}${d}`)),console.log(""),y.length>0){console.log(`${E}What's good:${d}`);for(let c of y)console.log(` ${F.LOW}[+]${d} ${w}${c}${d}`);console.log("")}if(t.length===0)console.log(` ${J}[PASS] No findings${d}`);else{console.log(`${E}Findings (${t.length}):${d}`);for(let c of t){let u=F[(c.severity||"").toUpperCase()]||"";console.log(`
|
|
13
|
+
${u}[${c.severity}]${d} ${E}${c.title}${d}`),c.resource&&c.resource!=="*"&&console.log(` ${w}Resource: ${c.resource}${d}`),c.description&&console.log(` ${w}${c.description}${d}`),c.remediation&&console.log(` ${J}Fix:${d} ${c.remediation}`)}}a&&typeof a.unitsUsed=="number"&&typeof a.cap=="number"&&(console.log(""),console.log(` ${w}Units used: ${a.unitsUsed}/${a.cap}${d}`)),e.demoInfo&&typeof e.demoInfo.analysesRemaining=="number"&&(console.log(""),console.log(` ${w}Demo analyses remaining: ${e.demoInfo.analysesRemaining}. Get a free key for more: https://www.shieldly.io/app/api${d}`)),console.log("")}function fe(e){return e==null?"":e>=80?"\x1B[32m":e>=50?"\x1B[33m":"\x1B[31m"}function he(e){let s=(e||"").toUpperCase();return F[s]||""}var $e=`
|
|
14
|
+
Usage: shieldly analyze-cf <template-file-or-dir> [options]
|
|
15
|
+
|
|
16
|
+
Analyze a CloudFormation template (or directory of templates) for security issues using AI.
|
|
11
17
|
|
|
12
18
|
Arguments:
|
|
13
|
-
template-file
|
|
19
|
+
template-file-or-dir Path to a JSON CF template, or a directory (e.g. cdk.out/)
|
|
20
|
+
containing synthesized stacks. All *.template.json files are
|
|
21
|
+
analyzed automatically.
|
|
14
22
|
|
|
15
23
|
Options:
|
|
16
24
|
--format <fmt> Output format: table | json (default: table)
|
|
17
25
|
--api-key <key> API key (or set SHIELDLY_API_KEY env var)
|
|
18
26
|
-h, --help Show this help
|
|
19
27
|
|
|
28
|
+
Authentication:
|
|
29
|
+
No key needed for demo mode (rate-limited). Set SHIELDLY_API_KEY for full access.
|
|
30
|
+
Get a free key: https://www.shieldly.io/app/api
|
|
31
|
+
|
|
20
32
|
Examples:
|
|
21
33
|
shieldly analyze-cf template.json
|
|
22
|
-
shieldly analyze-cf
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
shieldly analyze-cf cdk.out/
|
|
35
|
+
shieldly analyze-cf cdk.out/ --format json
|
|
36
|
+
|
|
37
|
+
CDK integration (add to package.json scripts):
|
|
38
|
+
"synth:check": "cdk synth && shieldly analyze-cf cdk.out/"
|
|
39
|
+
|
|
40
|
+
cdk.json hook (runs after every cdk synth):
|
|
41
|
+
{
|
|
42
|
+
"hooks": {
|
|
43
|
+
"afterSynth": ["sh", "-c", "shieldly analyze-cf cdk.out/ || true"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`;function ge(e){try{let s=(0,k.readFileSync)((0,Y.join)(e,"manifest.json"),"utf8"),o=JSON.parse(s);if(!o.artifacts||typeof o.artifacts!="object")return null;let n=Object.values(o.artifacts).filter(t=>t.type==="aws:cloudformation:stack"&&t.properties?.templateFile).map(t=>(0,Y.join)(e,t.properties.templateFile)).filter(t=>(0,k.existsSync)(t));return n.length>0?n:null}catch{return null}}function Ie(e){let s=ge(e);if(s)return s;let o=[],n;try{n=(0,k.readdirSync)(e,{withFileTypes:!0})}catch{return o}for(let t of n){if(!t.isFile())continue;let i=t.name;(i.endsWith(".template.json")||i.endsWith(".template.yaml"))&&o.push((0,Y.join)(e,i))}return o}async function ke(e,s,o,n,t){let i=(0,k.readFileSync)(e,"utf8");o!=="json"&&t>1?process.stdout.write(`[${n}/${t}] Analyzing ${e}\u2026
|
|
47
|
+
`):o!=="json"&&process.stdout.write(`Analyzing ${e}\u2026
|
|
48
|
+
`);let l=s?await A("/v1/analyze/cf",{template:i},s):await C("/api/demo/analyze-iam",{template:i,policyType:"cf"});return!s&&!l.cached&&j(),{filePath:e,data:l}}async function Z(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log($e);return}let s=e.indexOf("--format"),o=s!==-1?e[s+1]:"table";s!==-1&&!["table","json"].includes(o)&&(console.error(`Error: invalid --format "${o}". Use: table | json`),process.exit(1));let n=e.indexOf("--api-key"),t=S(n!==-1?e[n+1]:null),i=new Set;for(let r of[s,n])r!==-1&&(i.add(r),i.add(r+1));let l=e.find((r,c)=>!i.has(c)&&!r.startsWith("--"));l||(console.error("Error: template-file-or-dir argument is required"),process.exit(1)),!t&&_()&&(console.error(D()),process.exit(1)),!t&&o!=="json"&&console.log("Demo mode (rate-limited, no signup required). Get a free API key for higher limits: https://www.shieldly.io/app/api");let y=(0,k.statSync)(l,{throwIfNoEntry:!1});if(y||(console.error(`Error: path not found: ${l}`),process.exit(1)),y.isDirectory()){let r=Ie(l);r.length===0&&(console.error(`Error: no CloudFormation templates (*.template.json / *.template.yaml) found in ${l}
|
|
49
|
+
Run "cdk synth" first to generate stack templates.`),process.exit(1)),o!=="json"&&console.log(`Found ${r.length} stack template(s) in ${l}
|
|
50
|
+
`);let c=[],u=0,p=0;for(let f=0;f<r.length;f++){if(!t&&_()){o!=="json"&&console.error(`
|
|
51
|
+
Demo allowance reached \u2014 remaining stacks skipped. Get a free key: https://www.shieldly.io/app/api`);break}try{let{filePath:g,data:I}=await ke(r[f],t,o,f+1,r.length);c.push({filePath:g,data:I}),u+=(I.findings||[]).filter(M=>M.severity?.toUpperCase()==="CRITICAL").length,p+=(I.findings||[]).filter(M=>M.severity?.toUpperCase()==="HIGH").length,o!=="json"&&P(I,o)}catch(g){if(console.error(`Error analyzing ${r[f]}: ${g.message}`),!t&&/rate limit/i.test(g.message))break}}if(o==="json")console.log(JSON.stringify(c.map(({filePath:f,data:g})=>({stack:f,...g})),null,2));else if(r.length>1){let f=c.reduce((g,I)=>g+(I.data.findings||[]).length,0);console.log(`
|
|
52
|
+
Summary: ${r.length} stacks \xB7 ${f} total findings \xB7 ${u} critical \xB7 ${p} high`)}(u>0||p>0)&&process.exit(1);return}let a=R(l);o!=="json"&&console.log(`Analyzing ${l}\u2026`);try{let r=t?await A("/v1/analyze/cf",{template:a},t):await C("/api/demo/analyze-iam",{template:a,policyType:"cf"});P(r,o);let c=(r.findings||[]).filter(p=>p.severity?.toUpperCase()==="CRITICAL").length,u=(r.findings||[]).filter(p=>p.severity?.toUpperCase()==="HIGH").length;(c>0||u>0)&&process.exit(1)}catch(r){console.error(`Error: ${r.message}`),process.exit(1)}}var we=`
|
|
25
53
|
Usage: shieldly analyze-iam <policy-file> [options]
|
|
26
54
|
|
|
27
55
|
Analyze an AWS IAM policy for security issues using AI.
|
|
@@ -39,9 +67,9 @@ Options:
|
|
|
39
67
|
|
|
40
68
|
Examples:
|
|
41
69
|
shieldly analyze-iam policy.json
|
|
42
|
-
shieldly analyze-iam policy.json --type
|
|
70
|
+
shieldly analyze-iam policy.json --type cross_account --format json
|
|
43
71
|
SHIELDLY_API_KEY=sk_... shieldly analyze-iam policy.json
|
|
44
|
-
`;async function
|
|
72
|
+
`;async function Q(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log(we);return}let s=e.indexOf("--type"),o=s!==-1?e[s+1]:"identity",n=e.indexOf("--format"),t=n!==-1?e[n+1]:"table";s!==-1&&!["identity","iam_identity","cross_account"].includes(o)&&(console.error(`Error: invalid --type "${o}". Use: identity | cross_account`),process.exit(1)),n!==-1&&!["table","json"].includes(t)&&(console.error(`Error: invalid --format "${t}". Use: table | json`),process.exit(1));let i=e.indexOf("--api-key"),l=S(i!==-1?e[i+1]:null);!l&&_()&&(console.error(D()),process.exit(1));let y=new Set;for(let p of[s,n,i])p!==-1&&(y.add(p),y.add(p+1));let a=e.find((p,f)=>!y.has(f)&&!p.startsWith("--"));a||(console.error("Error: policy-file argument is required"),process.exit(1));let r=R(a);try{JSON.parse(r)}catch{console.error("Error: policy-file must be valid JSON"),process.exit(1)}let c=r.trim(),u=o==="identity"||o==="iam_identity"?"iam_identity":o==="cross_account"?"cross_account":"iam_identity";t!=="json"&&(console.log(`Analyzing ${a} (type: ${u})\u2026`),l||console.log("Demo mode (rate-limited, no signup required). Get a free API key for higher limits: https://www.shieldly.io/app/api"));try{let p=l?await A("/v1/analyze/iam",{policy:c,policyType:u},l):await C("/api/demo/analyze-iam",{policy:c,policyType:u});!l&&!p.cached&&j(),P(p,t);let f=(p.findings||[]).filter(I=>I.severity?.toUpperCase()==="CRITICAL").length,g=(p.findings||[]).filter(I=>I.severity?.toUpperCase()==="HIGH").length;(f>0||g>0)&&process.exit(1)}catch(p){console.error(`Error: ${p.message}`),process.exit(1)}}var X=`
|
|
45
73
|
Usage: shieldly api-keys <subcommand> [options]
|
|
46
74
|
|
|
47
75
|
Manage Shieldly API keys.
|
|
@@ -61,9 +89,12 @@ Examples:
|
|
|
61
89
|
shieldly api-keys list
|
|
62
90
|
shieldly api-keys create --label "CI/CD Key" --scopes iam,cf
|
|
63
91
|
shieldly api-keys revoke key_abc123
|
|
64
|
-
`,
|
|
65
|
-
|
|
66
|
-
|
|
92
|
+
`,x="\x1B[1m",ee="\x1B[2m",oe="\x1B[36m",h="\x1B[0m";function xe(e){return e?new Date(e).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}):"\u2014"}async function te(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log(X);return}let s=e[0],o=e.slice(1),n=o.indexOf("--api-key"),t=S(n!==-1?o[n+1]:null),i=o.indexOf("--format"),l=i!==-1?o[i+1]:"table";if(i!==-1&&!["table","json"].includes(l)&&(console.error(`Error: invalid --format "${l}". Use: table | json`),process.exit(1)),t||(console.error(`API key management requires an API key to authenticate.
|
|
93
|
+
|
|
94
|
+
Create your free account: https://www.shieldly.io/app/api
|
|
95
|
+
|
|
96
|
+
Set SHIELDLY_API_KEY or use --api-key once you have your key.`),process.exit(1)),s==="list"){try{let a=(await T("/v1/api-keys",t)).keys||[];if(l==="json"){console.log(JSON.stringify(a,null,2));return}if(a.length===0){console.log("No API keys found. Create one at https://www.shieldly.io/app/api");return}console.log(""),console.log(`${x}API Keys (${a.length}):${h}`),console.log(`${ee}${"\u2500".repeat(60)}${h}`);for(let r of a){let c=(r.scopes||[]).join(", ")||"all";console.log(` ${oe}${r.keyId}${h}`),console.log(` ${x}Label:${h} ${r.label||"(unlabeled)"}`),console.log(` ${x}Scopes:${h} ${c}`),console.log(` ${x}Uses:${h} ${r.usageCount||0}`),console.log(` ${x}Created:${h} ${xe(r.createdAt)}`),console.log("")}}catch(y){console.error(`Error: ${y.message}`),process.exit(1)}return}if(s==="create"){let y=o.indexOf("--label"),a=y!==-1?o[y+1]:"CLI Key",r=o.indexOf("--scopes"),u=(r!==-1?o[r+1]:"iam,cf").split(",").map(p=>p.trim()).filter(Boolean);try{let p=await A("/v1/api-keys",{label:a,scopes:u},t);if(l==="json"){console.log(JSON.stringify(p,null,2));return}console.log(""),console.log(`${x}[OK] API key created${h}`),console.log(` ${x}Key ID:${h} ${p.keyId}`),console.log(` ${x}API Key:${h} ${oe}${p.apiKey}${h}`),console.log(` ${ee}Store this key securely \u2014 it won't be shown again.${h}`),console.log("")}catch(p){console.error(`Error: ${p.message}`),process.exit(1)}return}if(s==="revoke"){let y=new Set;n!==-1&&(y.add(n),y.add(n+1)),i!==-1&&(y.add(i),y.add(i+1));let a=o.find((r,c)=>!y.has(c)&&!r.startsWith("--"));a||(console.error(`Error: key-id argument is required
|
|
97
|
+
Usage: shieldly api-keys revoke <key-id>`),process.exit(1));try{if(await V("/v1/api-keys",{keyId:a},t),l==="json"){console.log(JSON.stringify({success:!0,keyId:a}));return}console.log(`[OK] API key ${a} revoked`)}catch(r){console.error(`Error: ${r.message}`),process.exit(1)}return}console.error(`Unknown subcommand: ${s}`),console.log(X),process.exit(1)}var Ae=`
|
|
67
98
|
_shieldly() {
|
|
68
99
|
local cur prev words cword
|
|
69
100
|
_init_completion || return
|
|
@@ -97,7 +128,7 @@ _shieldly() {
|
|
|
97
128
|
return
|
|
98
129
|
;;
|
|
99
130
|
--type)
|
|
100
|
-
COMPREPLY=($(compgen -W "identity
|
|
131
|
+
COMPREPLY=($(compgen -W "identity cross_account" -- "$cur"))
|
|
101
132
|
return
|
|
102
133
|
;;
|
|
103
134
|
--format)
|
|
@@ -123,7 +154,7 @@ _shieldly() {
|
|
|
123
154
|
fi
|
|
124
155
|
} &&
|
|
125
156
|
complete -F _shieldly shieldly
|
|
126
|
-
`,
|
|
157
|
+
`,be=`
|
|
127
158
|
#compdef shieldly
|
|
128
159
|
|
|
129
160
|
_shieldly() {
|
|
@@ -145,7 +176,7 @@ _shieldly() {
|
|
|
145
176
|
args)
|
|
146
177
|
case $words[1] in
|
|
147
178
|
analyze-iam)
|
|
148
|
-
_arguments '--type[Policy type]:type:(identity
|
|
179
|
+
_arguments '--type[Policy type]:type:(identity cross_account)' '--format[Output format]:format:(table json)' '--api-key[API key]:api key' '(-h --help)'{-h,--help}'[Show help]' '*:policy file:_files' && ret=0
|
|
149
180
|
;;
|
|
150
181
|
analyze-cf)
|
|
151
182
|
_arguments '--format[Output format]:format:(table json)' '--api-key[API key]:api key' '(-h --help)'{-h,--help}'[Show help]' '*:template file:_files' && ret=0
|
|
@@ -194,7 +225,7 @@ _shieldly() {
|
|
|
194
225
|
}
|
|
195
226
|
|
|
196
227
|
_shieldly
|
|
197
|
-
`,
|
|
228
|
+
`,Ee=`
|
|
198
229
|
Usage: shieldly completion <shell>
|
|
199
230
|
|
|
200
231
|
Generate shell completion scripts for the shieldly CLI.
|
|
@@ -208,42 +239,45 @@ Examples:
|
|
|
208
239
|
eval "$(shieldly completion bash)" # Source bash completion
|
|
209
240
|
shieldly completion zsh > /usr/local/share/zsh/site-functions/_shieldly # Install zsh completion
|
|
210
241
|
shieldly completion install # Auto-detect and install
|
|
211
|
-
`;async function
|
|
242
|
+
`;async function se(e){if(!e.length||e.includes("-h")||e.includes("--help")){console.log(Ee);return}let s=e[0];switch(s){case"bash":console.log(Ae.trimStart());break;case"zsh":console.log(be.trimStart());break;case"install":await Se();break;default:console.error(`Unsupported shell: ${s}`),console.log("Supported shells: bash, zsh"),process.exit(1)}}async function Se(){let e=process.env.SHELL||"",s=process.env.HOME||"";if(e.includes("zsh")){let o=`${s}/.zshrc`,n=`
|
|
212
243
|
# shieldly CLI completion
|
|
213
244
|
autoload -Uz compinit && compinit -C 2>/dev/null
|
|
214
|
-
eval "$(shieldly completion zsh)" 2>/dev/null`;try{let{readFileSync:
|
|
245
|
+
eval "$(shieldly completion zsh)" 2>/dev/null`;try{let{readFileSync:t,appendFileSync:i,existsSync:l}=await import("node:fs");if(l(o)&&t(o,"utf8").includes("shieldly completion")){console.log("shieldly completion already installed in ~/.zshrc");return}i(o,n),console.log("Installed shieldly completion in ~/.zshrc"),console.log("Run: source ~/.zshrc")}catch(t){console.error(`Failed to install: ${t.message}`),process.exit(1)}}else if(e.includes("bash")){let o=`${s}/.bashrc`,n=`
|
|
215
246
|
# shieldly CLI completion
|
|
216
|
-
source /dev/stdin <<< "$(shieldly completion bash)" 2>/dev/null`;try{let{readFileSync:
|
|
217
|
-
${
|
|
247
|
+
source /dev/stdin <<< "$(shieldly completion bash)" 2>/dev/null`;try{let{readFileSync:t,appendFileSync:i,existsSync:l}=await import("node:fs");if(l(o)&&t(o,"utf8").includes("shieldly completion")){console.log("shieldly completion already installed in ~/.bashrc");return}i(o,n),console.log("Installed shieldly completion in ~/.bashrc"),console.log("Run: source ~/.bashrc")}catch(t){console.error(`Failed to install: ${t.message}`),process.exit(1)}}else console.error(`Unsupported shell: ${e}. Install manually:`),console.log(' eval "$(shieldly completion bash)" # For bash'),console.log(' eval "$(shieldly completion zsh)" # For zsh'),process.exit(1)}var b="\x1B[1m",L="\x1B[36m",v="\x1B[2m",m="\x1B[0m",_e="1.0.3",Ce=`
|
|
248
|
+
${b}shieldly${m} \u2014 AI-Powered Security Analysis for AWS
|
|
218
249
|
|
|
219
|
-
${
|
|
250
|
+
${b}Usage:${m}
|
|
220
251
|
shieldly <command> [args] [options]
|
|
221
252
|
|
|
222
|
-
${
|
|
223
|
-
${
|
|
224
|
-
${
|
|
225
|
-
${
|
|
226
|
-
${
|
|
253
|
+
${b}Commands:${m}
|
|
254
|
+
${L}analyze-iam${m} <policy-file> Analyze an IAM policy for security issues
|
|
255
|
+
${L}analyze-cf${m} <template-file-or-dir> Analyze a CloudFormation template or CDK output directory
|
|
256
|
+
${L}api-keys${m} list|create|revoke Manage API keys
|
|
257
|
+
${L}completion${m} bash|zsh|install Generate shell completion
|
|
227
258
|
|
|
228
|
-
${
|
|
259
|
+
${b}Global Options:${m}
|
|
229
260
|
--api-key <key> API key (or set SHIELDLY_API_KEY env var)
|
|
230
261
|
--version Show version
|
|
231
262
|
-h, --help Show this help
|
|
232
263
|
|
|
233
|
-
${
|
|
264
|
+
${b}Authentication:${m}
|
|
234
265
|
Set your API key via env var: export SHIELDLY_API_KEY=sk_...
|
|
235
|
-
Get a free API key at: ${
|
|
266
|
+
Get a free API key at: ${L}https://www.shieldly.io/app/api${m}
|
|
236
267
|
|
|
237
|
-
${
|
|
238
|
-
${
|
|
268
|
+
${b}Examples:${m}
|
|
269
|
+
${v}# Analyze an IAM policy or CF template (no API key needed \u2014 demo mode)${m}
|
|
239
270
|
shieldly analyze-iam policy.json
|
|
240
271
|
|
|
241
|
-
${
|
|
242
|
-
shieldly analyze-cf template.
|
|
272
|
+
${v}# Analyze a single CloudFormation template${m}
|
|
273
|
+
shieldly analyze-cf template.json
|
|
274
|
+
|
|
275
|
+
${v}# Scan all CDK stacks after synthesis (reads manifest.json \u2014 current stacks only)${m}
|
|
276
|
+
cdk synth && shieldly analyze-cf cdk.out/
|
|
243
277
|
|
|
244
|
-
${
|
|
278
|
+
${v}# List API keys${m}
|
|
245
279
|
shieldly api-keys list
|
|
246
280
|
|
|
247
|
-
${
|
|
281
|
+
${v}# Use in CI${m}
|
|
248
282
|
SHIELDLY_API_KEY=\${{ secrets.SHIELDLY_API_KEY }} shieldly analyze-iam policy.json
|
|
249
|
-
`;async function
|
|
283
|
+
`;async function Pe(){let[,,e,...s]=process.argv;switch((!e||e==="-h"||e==="--help")&&(console.log(Ce),process.exit(0)),(e==="--version"||e==="-v")&&(console.log(_e),process.exit(0)),e){case"analyze-iam":await Q(s);break;case"analyze-cf":await Z(s);break;case"api-keys":await te(s);break;case"completion":await se(s);break;default:console.error(`Unknown command: ${e}`),console.log(`Run ${b}shieldly --help${m} for usage`),process.exit(1)}}Pe().catch(e=>{console.error("Fatal error:",e.message),process.exit(1)});
|