[{"data":1,"prerenderedAt":3154},["ShallowReactive",2],{"navigation":3,"\u002Fdocs\u002Fsurface":135,"\u002Fdocs\u002Fsurface-surround":3149},[4],{"title":5,"path":6,"stem":7,"children":8,"page":134},"Docs","\u002Fdocs","docs",[9,13,17,21,25,29,33,37,41,45,49,53,57,61,70,74,78,82,86,90,94,98,102,106,110,114,118,122,126,130],{"title":10,"path":11,"stem":12},"AI harnesses: LLM tools, MCP servers and agents","\u002Fdocs\u002Fai-tools","docs\u002Fai-tools",{"title":14,"path":15,"stem":16},"Architecture","\u002Fdocs\u002Farchitecture","docs\u002Farchitecture",{"title":18,"path":19,"stem":20},"Web \u002F native bridges","\u002Fdocs\u002Fbridges","docs\u002Fbridges",{"title":22,"path":23,"stem":24},"Broadcast channels and tests","\u002Fdocs\u002Fchannels-and-tests","docs\u002Fchannels-and-tests",{"title":26,"path":27,"stem":28},"CLI reference","\u002Fdocs\u002Fcli","docs\u002Fcli",{"title":30,"path":31,"stem":32},"Completeness: how far an answer reaches","\u002Fdocs\u002Fcompleteness","docs\u002Fcompleteness",{"title":34,"path":35,"stem":36},"Configuration","\u002Fdocs\u002Fconfiguration","docs\u002Fconfiguration",{"title":38,"path":39,"stem":40},"External systems","\u002Fdocs\u002Fexternal","docs\u002Fexternal",{"title":42,"path":43,"stem":44},"Generated, copied and vendored files","\u002Fdocs\u002Fgenerated","docs\u002Fgenerated",{"title":46,"path":47,"stem":48},"Installing and updating cg","\u002Fdocs\u002Finstall","docs\u002Finstall",{"title":50,"path":51,"stem":52},"Java","\u002Fdocs\u002Fjava","docs\u002Fjava",{"title":54,"path":55,"stem":56},"Kotlin","\u002Fdocs\u002Fkotlin","docs\u002Fkotlin",{"title":58,"path":59,"stem":60},"Known limitations","\u002Fdocs\u002Flimitations","docs\u002Flimitations",{"title":62,"path":63,"stem":64,"children":65},"MCP server","\u002Fdocs\u002Fmcp","docs\u002Fmcp",[66],{"title":67,"path":68,"stem":69},"code-graph MCP: sample outputs (real calls via the MCP Python SDK stdio client)","\u002Fdocs\u002Fmcp\u002Fsample_outputs","docs\u002Fmcp\u002Fsample_outputs",{"title":71,"path":72,"stem":73},"Rust, C and C++","\u002Fdocs\u002Fnative","docs\u002Fnative",{"title":75,"path":76,"stem":77},"Port gaps: cg parity","\u002Fdocs\u002Fparity","docs\u002Fparity",{"title":79,"path":80,"stem":81},"PHP","\u002Fdocs\u002Fphp","docs\u002Fphp",{"title":83,"path":84,"stem":85},"Planned changes","\u002Fdocs\u002Fplans","docs\u002Fplans",{"title":87,"path":88,"stem":89},"Platform-specific code","\u002Fdocs\u002Fplatforms","docs\u002Fplatforms",{"title":91,"path":92,"stem":93},"Protocol links","\u002Fdocs\u002Fprotocols","docs\u002Fprotocols",{"title":95,"path":96,"stem":97},"Python","\u002Fdocs\u002Fpython","docs\u002Fpython",{"title":99,"path":100,"stem":101},"Quick start","\u002Fdocs\u002Fquickstart","docs\u002Fquickstart",{"title":103,"path":104,"stem":105},"Graph schema","\u002Fdocs\u002Fschema","docs\u002Fschema",{"title":107,"path":108,"stem":109},"Attack surface","\u002Fdocs\u002Fsurface","docs\u002Fsurface",{"title":111,"path":112,"stem":113},"Swift","\u002Fdocs\u002Fswift","docs\u002Fswift",{"title":115,"path":116,"stem":117},"TypeScript \u002F JavaScript frameworks","\u002Fdocs\u002Fts-frameworks","docs\u002Fts-frameworks",{"title":119,"path":120,"stem":121},"How we validate","\u002Fdocs\u002Fvalidation","docs\u002Fvalidation",{"title":123,"path":124,"stem":125},"Validation log","\u002Fdocs\u002Fvalidation-log","docs\u002Fvalidation-log",{"title":127,"path":128,"stem":129},"Value facts and resolutions","\u002Fdocs\u002Fvalue-facts","docs\u002Fvalue-facts",{"title":131,"path":132,"stem":133},"Visual view","\u002Fdocs\u002Fviz","docs\u002Fviz",false,{"id":136,"title":107,"body":137,"description":3143,"extension":3144,"meta":3145,"navigation":3146,"path":108,"seo":3147,"stem":109,"__hash__":3148},"docs\u002Fdocs\u002Fsurface.md",{"type":138,"value":139,"toc":3120},"minimark",[140,154,250,255,315,347,351,554,562,580,766,789,818,822,835,1164,1178,1184,1218,1434,1491,1497,1523,1760,1840,1930,1988,1996,2005,2057,2063,2218,2236,2289,2306,2312,2321,2327,2330,2336,2340,2481,2509,2513,2533,2616,2620,2631,2766,2787,2791,2830,2833,3089,3093,3106,3110,3116],[141,142,143,147,148,153],"p",{},[144,145,146],"code",{},"cg surface"," reads facts the index already holds and answers three questions: what does the code expose, what does it\nconnect to, and where are credentials and transport protection weak. Nothing is rescanned: the checks that need source\ntext (disabled verification, insecure gRPC channels, risky IPC settings) run once at index time and leave facts on the\ngraph. A secret value is never stored or printed: a finding names the kind and the location, and the index keeps only a\nredacted marker for a credential literal (",[149,150,152],"a",{"href":151},"#no-secret-values-in-the-index","No secret values in the index",").",[155,156,161],"pre",{"className":157,"code":158,"language":159,"meta":160,"style":160},"language-bash shiki shiki-themes cg-light cg-light cg-dark","cg surface --db out\u002Fgraph.db                          # findings, grouped by type then protocol\ncg surface --db out\u002Fgraph.db --inbound --outbound     # plus both surface listings\ncg surface --db out\u002Fgraph.db --format sarif > cg.sarif\ncg surface --db out\u002Fgraph.db --fail-on hardcoded,unverified   # exit 1 in CI\n","bash","",[144,162,163,186,206,230],{"__ignoreMap":160},[164,165,168,172,176,179,182],"span",{"class":166,"line":167},"line",1,[164,169,171],{"class":170},"sZJo5","cg",[164,173,175],{"class":174},"sha9Y"," surface",[164,177,178],{"class":174}," --db",[164,180,181],{"class":174}," out\u002Fgraph.db",[164,183,185],{"class":184},"seS0s","                          # findings, grouped by type then protocol\n",[164,187,189,191,193,195,197,200,203],{"class":166,"line":188},2,[164,190,171],{"class":170},[164,192,175],{"class":174},[164,194,178],{"class":174},[164,196,181],{"class":174},[164,198,199],{"class":174}," --inbound",[164,201,202],{"class":174}," --outbound",[164,204,205],{"class":184},"     # plus both surface listings\n",[164,207,209,211,213,215,217,220,223,227],{"class":166,"line":208},3,[164,210,171],{"class":170},[164,212,175],{"class":174},[164,214,178],{"class":174},[164,216,181],{"class":174},[164,218,219],{"class":174}," --format",[164,221,222],{"class":174}," sarif",[164,224,226],{"class":225},"sZ_TN"," >",[164,228,229],{"class":174}," cg.sarif\n",[164,231,233,235,237,239,241,244,247],{"class":166,"line":232},4,[164,234,171],{"class":170},[164,236,175],{"class":174},[164,238,178],{"class":174},[164,240,181],{"class":174},[164,242,243],{"class":174}," --fail-on",[164,245,246],{"class":174}," hardcoded,unverified",[164,248,249],{"class":184},"   # exit 1 in CI\n",[251,252,254],"h2",{"id":253},"what-it-lists","What it lists",[256,257,258,274],"table",{},[259,260,261],"thead",{},[262,263,264,268,271],"tr",{},[265,266,267],"th",{},"surface",[265,269,270],{},"items",[265,272,273],{},"per item",[275,276,277,295],"tbody",{},[262,278,279,285,288],{},[280,281,282],"td",{},[144,283,284],{},"--inbound",[280,286,287],{},"HTTP routes, webhooks, WebSocket \u002F Socket.IO \u002F SSE, gRPC \u002F GraphQL \u002F tRPC \u002F JSON-RPC handlers, message consumers, TCP \u002F UDP listeners, Unix sockets, modelled IPC handlers (Electron, Tauri, Android)",[280,289,290,291,294],{},"guard state, whether it reaches a write (as ",[144,292,293],{},"cg routes --writes","), protocol, transport, bind exposure for listeners",[262,296,297,302,312],{},[280,298,299],{},[144,300,301],{},"--outbound",[280,303,304,305,308,309,311],{},"every ",[144,306,307],{},"external:"," system (",[149,310,38],{"href":39},")",[280,313,314],{},"address source, credential source, TLS, entry points that reach it",[141,316,317,318,321,322,325,326,329,330,333,334,338,339,342,343,346],{},"Guard state is ",[144,319,320],{},"guarded",", ",[144,323,324],{},"secret-checked"," (a signature or shared-secret check), ",[144,327,328],{},"inline-guarded"," (an unconditional\ncheck inside the action), ",[144,331,332],{},"public"," (a route in the ",[149,335,337],{"href":336},"#public-by-design-routes","public-by-design table","), ",[144,340,341],{},"unguarded",", or\n",[144,344,345],{},"unchecked"," when the protocol records no guards. Socket.IO handlers in a\nclient bundle are not an inbound surface and are left out.",[251,348,350],{"id":349},"findings","Findings",[256,352,353,366],{},[259,354,355],{},[262,356,357,360,363],{},[265,358,359],{},"finding",[265,361,362],{},"triggers",[265,364,365],{},"severity",[275,367,368,409,453,467,495,514,526,541],{},[262,369,370,375,406],{},[280,371,372],{},[144,373,374],{},"hardcoded",[280,376,377,378,321,381,384,385,388,389,321,392,321,395,321,398,401,402,405],{},"an external system or SDK call with a literal credential (",[144,379,380],{},"credential_source=literal",[144,382,383],{},"credential_literal",", an edge with ",[144,386,387],{},"literal_credential","); a DSN with a password in it; a config key named like a secret (",[144,390,391],{},"password",[144,393,394],{},"token",[144,396,397],{},"secret",[144,399,400],{},"*_key",") with a literal value, or an ",[144,403,404],{},"env('X', 'literal')"," default for a secret-looking variable",[280,407,408],{},"high",[262,410,411,416,450],{},[280,412,413],{},[144,414,415],{},"plaintext",[280,417,418,419,422,423,321,426,321,429,321,432,321,435,321,438,441,442,445,446,449],{},"an external system with ",[144,420,421],{},"tls=false"," and a non-loopback host (",[144,424,425],{},"http:\u002F\u002F",[144,427,428],{},"ws:\u002F\u002F",[144,430,431],{},"redis:\u002F\u002F",[144,433,434],{},"amqp:\u002F\u002F",[144,436,437],{},"ftp:\u002F\u002F",[144,439,440],{},"ldap:\u002F\u002F",", plain SMTP, a Docker TCP host); also Telnet \u002F TFTP, MQTT on 1883 and SMTP on 25 at ",[144,443,444],{},"heuristic","; and a gRPC channel or port created without TLS (protocol ",[144,447,448],{},"grpc",", see below)",[280,451,452],{},"medium",[262,454,455,460,465],{},[280,456,457],{},[144,458,459],{},"unverified",[280,461,462,463,311],{},"a webhook route that reads a provider header and never verifies it (",[149,464,91],{"href":92},[280,466,408],{},[262,468,469,473,489],{},[280,470,471],{},[144,472,341],{},[280,474,475,476,480,481,484,485,488],{},"a route or handler with no guard after the ",[149,477,479],{"href":478},"#how-unguarded-decides","guard rules",": the ",[144,482,483],{},"cg routes --unguarded"," rule plus framework defaults, checks at the top of the handler and the public-route table; ",[144,486,487],{},"--strict"," keeps route guards only",[280,490,491,492,494],{},"medium when nothing looks like a check and a write is reached; low otherwise; ",[144,493,444],{}," confidence when unclear",[262,496,497,502,512],{},[280,498,499],{},[144,500,501],{},"exposed-listener",[280,503,504,505,321,508,511],{},"a TCP \u002F UDP listener bound to all interfaces (",[144,506,507],{},"0.0.0.0",[144,509,510],{},"::",", no host)",[280,513,452],{},[262,515,516,521,524],{},[280,517,518],{},[144,519,520],{},"tls-off",[280,522,523],{},"certificate verification or SSH host key checking switched off in code or configuration (table below)",[280,525,408],{},[262,527,528,533,539],{},[280,529,530],{},[144,531,532],{},"ipc-exposed",[280,534,535,536,538],{},"local IPC open to callers it should not trust (table below), for what ",[144,537,341],{}," does not already report",[280,540,452],{},[262,542,543,548,551],{},[280,544,545],{},[144,546,547],{},"ssrf",[280,549,550],{},"the host or the whole URL of an outbound HTTP call is built from request input and no allow-list check precedes it (see \"Outbound URLs from request input\"); a DNS lookup of request input is reported at the lower severity",[280,552,553],{},"high (DNS lookup of input: medium)",[555,556,558,559,561],"h3",{"id":557},"how-unguarded-decides","How ",[144,560,341],{}," decides",[141,563,564,566,567,570,571,573,574,576,577,153],{},[144,565,146],{}," starts from the ",[144,568,569],{},"cg routes"," guard state and then, unless ",[144,572,487],{}," is given, applies these rules in order. A route that gets a guard from a rule is not reported; a rule that cannot decide leaves it reported at ",[144,575,444],{}," confidence and low severity, with the reason in the detail (",[144,578,579],{},"unclear: ...",[256,581,582,595],{},[259,583,584],{},[262,585,586,589,592],{},[265,587,588],{},"step",[265,590,591],{},"rule",[265,593,594],{},"by framework",[275,596,597,683,697,753],{},[262,598,599,602,605],{},[280,600,601],{},"1. project defaults",[280,603,604],{},"a guard that applies to every route of the project is added to the route",[280,606,607,608,611,612,615,616,619,620,623,624,627,628,631,632,635,636,631,639,642,643,646,647,650,651,321,654,321,657,660,661,664,665,668,669,672,673,321,676,627,679,682],{},"Django REST framework: ",[144,609,610],{},"REST_FRAMEWORK"," ",[144,613,614],{},"DEFAULT_PERMISSION_CLASSES"," of the settings file (an ",[144,617,618],{},"AllowAny"," default is not a guard; a view with ",[144,621,622],{},"permission_classes = []"," is explicitly public); Laravel: global ",[144,625,626],{},"$middleware"," and ",[144,629,630],{},"$middlewareGroups"," of ",[144,633,634],{},"app\u002FHttp\u002FKernel.php"," or ",[144,637,638],{},"withMiddleware()",[144,640,641],{},"bootstrap\u002Fapp.php",", by the group of the route file; Nest: ",[144,644,645],{},"APP_GUARD"," providers (already recorded), removed from a route that has ",[144,648,649],{},"@Public()"," or a similar opt-out decorator (",[144,652,653],{},"SkipAuth",[144,655,656],{},"NoAuth",[144,658,659],{},"AllowAnonymous",", ...); Express: ",[144,662,663],{},"app.use(auth)"," that comes before the route in the same router and whose mount path covers it (",[144,666,667],{},"app.use('\u002Fadmin', auth)"," guards ",[144,670,671],{},"\u002Fadmin\u002F*"," only); Express ",[144,674,675],{},"session()",[144,677,678],{},"cookie-session",[144,680,681],{},"express-session"," are never auth",[262,684,685,688,694],{},[280,686,687],{},"2. public by design",[280,689,690,691],{},"the route matches the ",[149,692,693],{"href":336},"public table",[280,695,696],{},"all",[262,698,699,702,750],{},[280,700,701],{},"3. check in the handler",[280,703,704,705,708,709,712,713,716,717,321,720,321,723,321,726,635,729,732,733,635,736,739,740,321,743,627,746,749],{},"within the first 14 code lines of the handler (not past its end): an ",[144,706,707],{},"if"," \u002F ",[144,710,711],{},"unless"," whose condition mentions a session, user, token, permission, role, admin flag, ",[144,714,715],{},"Authorization"," or API key and that returns, throws, aborts, redirects or answers 401 \u002F 403; or a leading call such as ",[144,718,719],{},"requireAuth()",[144,721,722],{},"authorize()",[144,724,725],{},"verify...()",[144,727,728],{},"check...()",[144,730,731],{},"abort_unless()"," whose name or argument mentions the user, auth, token, role, permission, signature or key. A Socket.IO endpoint is checked in its connection handler first (",[144,734,735],{},"io.use(...)",[144,737,738],{},"on('connection')","), then in its own first statements. Laravel ",[144,741,742],{},"auth()->check()",[144,744,745],{},"Auth::user()",[144,747,748],{},"$request->user()"," are inline guards",[280,751,752],{},"all; PHP, Python and TypeScript",[262,754,755,758,761],{},[280,756,757],{},"4. unclear",[280,759,760],{},"a middleware or decorator whose name looks like a check but is not in the presets, a webhook receiver whose verification is not recognised, a call in the handler whose name looks like a check, a GraphQL endpoint (access is per field)",[280,762,763,765],{},[144,764,444],{},", low",[141,767,768,769,771,772,774,775,778,779,782,783,785,786,788],{},"Severity: ",[144,770,452],{}," when no step applied, nothing in the handler looks like a check, and the handler reaches a database write (",[144,773,293],{},"); ",[144,776,777],{},"low"," for a read-only handler, a ",[149,780,781],{"href":336},"metrics endpoint"," and every ",[144,784,444],{}," case. ",[144,787,487],{}," reports every route with no route-level guard at medium severity, as before part 2d.",[141,790,791,792,795,796,321,799,802,803,321,805,321,808,321,811,814,815,817],{},"Per-framework notes: Django resolves a DRF view routed by a plain ",[144,793,794],{},"path()"," as a DRF view, so the settings default applies; Laravel resolves the route file's group (",[144,797,798],{},"web",[144,800,801],{},"api",") and the global stack; Nest reads ",[144,804,649],{},[144,806,807],{},"@SkipAuth()",[144,809,810],{},"@AllowAnonymous()",[144,812,813],{},"@NoAuth()"," and similar decorators as opt-outs; Express guards depend on registration order, because ",[144,816,663],{}," guards only routes registered after it.",[555,819,821],{"id":820},"public-by-design-routes","Public-by-design routes",[141,823,824,825,827,828,830,831,834],{},"These patterns are matched on the route path (segments are case-insensitive, parameters are ignored). A match makes the route ",[144,826,332],{}," and removes it from ",[144,829,341],{},". ",[144,832,833],{},"metrics"," is the exception: it stays reported, at low severity.",[256,836,837,847],{},[259,838,839],{},[262,840,841,844],{},[265,842,843],{},"class",[265,845,846],{},"examples of matching path segments",[275,848,849,883,920,948,990,1005,1036,1058,1074,1085,1099,1121,1149],{},[262,850,851,854],{},[280,852,853],{},"sign-in \u002F sign-up \u002F password reset",[280,855,856,321,859,321,862,321,865,321,868,321,871,321,874,321,877,321,880],{},[144,857,858],{},"login",[144,860,861],{},"logout",[144,863,864],{},"signin",[144,866,867],{},"signup",[144,869,870],{},"register",[144,872,873],{},"forgot-password",[144,875,876],{},"reset-password",[144,878,879],{},"verify-email",[144,881,882],{},"csrf",[262,884,885,888],{},[280,886,887],{},"health check",[280,889,890,321,893,321,896,321,899,321,902,321,905,321,908,321,911,321,914,321,917],{},[144,891,892],{},"health",[144,894,895],{},"healthz",[144,897,898],{},"ready",[144,900,901],{},"readyz",[144,903,904],{},"live",[144,906,907],{},"livez",[144,909,910],{},"ping",[144,912,913],{},"status",[144,915,916],{},"up",[144,918,919],{},"alive",[262,921,922,925],{},[280,923,924],{},"robots \u002F sitemap \u002F favicon \u002F manifest",[280,926,927,321,930,321,933,321,936,321,939,321,942,321,945],{},[144,928,929],{},"robots.txt",[144,931,932],{},"sitemap*.xml",[144,934,935],{},"favicon.ico",[144,937,938],{},"manifest.json",[144,940,941],{},"security.txt",[144,943,944],{},"humans.txt",[144,946,947],{},"ads.txt",[262,949,950,953],{},[280,951,952],{},"static assets, media, thumbnails",[280,954,955,321,958,321,961,321,963,321,966,321,969,321,972,321,975,321,978,321,981,321,984,321,987],{},[144,956,957],{},"static",[144,959,960],{},"assets",[144,962,332],{},[144,964,965],{},"media",[144,967,968],{},"_next",[144,970,971],{},"locales",[144,973,974],{},"fonts",[144,976,977],{},"images",[144,979,980],{},"thumbnails",[144,982,983],{},"avatars",[144,985,986],{},"emoji",[144,988,989],{},"logo",[262,991,992,998],{},[280,993,994,995],{},"jwks \u002F ",[144,996,997],{},".well-known",[280,999,1000,321,1002],{},[144,1001,997],{},[144,1003,1004],{},"jwks",[262,1006,1007,1010],{},[280,1008,1009],{},"OAuth \u002F OIDC callback or token endpoint",[280,1011,1012,321,1015,321,1018,321,1021,321,1024,321,1027,321,1030,321,1033],{},[144,1013,1014],{},"oauth",[144,1016,1017],{},"oidc",[144,1019,1020],{},"openid",[144,1022,1023],{},"callback",[144,1025,1026],{},"authorize",[144,1028,1029],{},"sso",[144,1031,1032],{},"saml",[144,1034,1035],{},"\u002Ftoken",[262,1037,1038,1041],{},[280,1039,1040],{},"API schema \u002F documentation",[280,1042,1043,321,1046,321,1049,321,1052,321,1055],{},[144,1044,1045],{},"swagger",[144,1047,1048],{},"redoc",[144,1050,1051],{},"openapi",[144,1053,1054],{},"api-docs",[144,1056,1057],{},"\u002Fapi\u002Fschema",[262,1059,1060,1063],{},[280,1061,1062],{},"utility endpoint",[280,1064,1065,321,1068,321,1071],{},[144,1066,1067],{},"version",[144,1069,1070],{},"ip",[144,1072,1073],{},"geolocation",[262,1075,1076,1079],{},[280,1077,1078],{},"scheduled job endpoint",[280,1080,1081,1084],{},[144,1082,1083],{},"cron"," (the job checks its own key)",[262,1086,1087,1090],{},[280,1088,1089],{},"tRPC adapter, Auth.js \u002F NextAuth handler",[280,1091,1092,1095,1096],{},[144,1093,1094],{},"\u002Fapi\u002Ftrpc\u002F*"," (each procedure carries its own middleware), ",[144,1097,1098],{},"[...nextauth]",[262,1100,1101,1104],{},[280,1102,1103],{},"embed pages, shared link, site root",[280,1105,1106,321,1109,321,1112,321,1115,321,1118],{},[144,1107,1108],{},"embeds",[144,1110,1111],{},"\u002Fs\u002F",[144,1113,1114],{},"\u002Fshare\u002F",[144,1116,1117],{},"\u002Fpublic\u002F",[144,1119,1120],{},"\u002F",[262,1122,1123,1126],{},[280,1124,1125],{},"test \u002F fixture code",[280,1127,1128,321,1131,321,1134,321,1137,321,1140,321,1143,321,1146],{},[144,1129,1130],{},"test",[144,1132,1133],{},"tests",[144,1135,1136],{},"__tests__",[144,1138,1139],{},"e2e",[144,1141,1142],{},"fixtures",[144,1144,1145],{},"*.test",[144,1147,1148],{},"*.spec",[262,1150,1151,1154],{},[280,1152,1153],{},"metrics (low severity, still reported)",[280,1155,1156,321,1158,321,1161],{},[144,1157,833],{},[144,1159,1160],{},"prometheus",[144,1162,1163],{},"stats",[141,1165,1166,1167,1170,1171,1173,1174,1177],{},"The table is a heuristic about names. A route named ",[144,1168,1169],{},"\u002Fstatus"," that is not public is hidden by it; add nothing to the graph for that, but use ",[144,1172,487],{}," to see every route without a route-level guard. A public route that is not in the table needs a ",[144,1175,1176],{},"surface.ignore"," entry.",[555,1179,1181,1182,311],{"id":1180},"disabled-verification-tls-off","Disabled verification (",[144,1183,520],{},[141,1185,1186,1187,1190,1191,1194,1195,1198,1199,1202,1203,1206,1207,1210,1211,627,1214,1217],{},"The index records each hit on the function or method that contains it (the module node at file level, a\n",[144,1188,1189],{},"file:config:\u003Cpath>"," node for scripts and config files), as ",[144,1192,1193],{},"attrs.insecure_transport",". Test files and test callers are\nskipped and counted (",[144,1196,1197],{},"tests_skipped"," in the index stats). ",[144,1200,1201],{},".env"," files are never read: only ",[144,1204,1205],{},".env.example"," style files,\ncompose files, Dockerfiles, ",[144,1208,1209],{},"package.json"," scripts, CI files, shell scripts, ",[144,1212,1213],{},"ssh_config",[144,1215,1216],{},"ansible.cfg",".",[256,1219,1220,1233],{},[259,1221,1222],{},[262,1223,1224,1227,1230],{},[265,1225,1226],{},"language",[265,1228,1229],{},"TLS verification off",[265,1231,1232],{},"SSH host key not checked",[275,1234,1235,1270,1296,1326,1355,1370,1385,1397],{},[262,1236,1237,1239,1257],{},[280,1238,95],{},[280,1240,1241,1244,1245,321,1248,321,1251,321,1254],{},[144,1242,1243],{},"verify=False"," (requests, httpx, urllib3), aiohttp ",[144,1246,1247],{},"ssl=False",[144,1249,1250],{},"ssl._create_unverified_context",[144,1252,1253],{},"CERT_NONE",[144,1255,1256],{},"check_hostname = False",[280,1258,1259,1260,708,1263,1266,1267],{},"paramiko ",[144,1261,1262],{},"AutoAddPolicy",[144,1264,1265],{},"WarningPolicy",", asyncssh ",[144,1268,1269],{},"known_hosts=None",[262,1271,1272,1275,1290],{},[280,1273,1274],{},"TypeScript \u002F JavaScript",[280,1276,1277,1280,1281,338,1284,321,1287],{},[144,1278,1279],{},"rejectUnauthorized: false"," (https, tls, undici, got, axios ",[144,1282,1283],{},"httpsAgent",[144,1285,1286],{},"strictSSL: false",[144,1288,1289],{},"process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'",[280,1291,1292,1293],{},"ssh2 ",[144,1294,1295],{},"hostVerifier: () => true",[262,1297,1298,1300,1320],{},[280,1299,79],{},[280,1301,1302,1305,1306,1309,1310,321,1313,1316,1317],{},[144,1303,1304],{},"CURLOPT_SSL_VERIFYPEER"," false, ",[144,1307,1308],{},"CURLOPT_SSL_VERIFYHOST"," 0, Guzzle ",[144,1311,1312],{},"'verify' => false",[144,1314,1315],{},"Http::withoutVerifying()",", stream context ",[144,1318,1319],{},"verify_peer(_name) => false",[280,1321,1322,1325],{},[144,1323,1324],{},"StrictHostKeyChecking=no"," in strings",[262,1327,1328,1331,1349],{},[280,1329,1330],{},"Kotlin \u002F Java",[280,1332,1333,1334,1337,1338,1341,1342,1345,1346],{},"trust-all ",[144,1335,1336],{},"X509TrustManager"," (empty ",[144,1339,1340],{},"checkServerTrusted","), a ",[144,1343,1344],{},"HostnameVerifier"," that returns true, OkHttp ",[144,1347,1348],{},"hostnameVerifier { _, _ -> true }",[280,1350,1351,1352],{},"JSch ",[144,1353,1354],{},"setConfig(\"StrictHostKeyChecking\", \"no\")",[262,1356,1357,1360,1368],{},[280,1358,1359],{},"Rust",[280,1361,1362,708,1365],{},[144,1363,1364],{},"danger_accept_invalid_certs(true)",[144,1366,1367],{},"danger_accept_invalid_hostnames(true)",[280,1369],{},[262,1371,1372,1375,1380],{},[280,1373,1374],{},"Go",[280,1376,1377],{},[144,1378,1379],{},"InsecureSkipVerify: true",[280,1381,1382],{},[144,1383,1384],{},"ssh.InsecureIgnoreHostKey()",[262,1386,1387,1390,1395],{},[280,1388,1389],{},"Dart",[280,1391,1392],{},[144,1393,1394],{},"badCertificateCallback = (...) => true",[280,1396],{},[262,1398,1399,1402,1414],{},[280,1400,1401],{},"scripts, config",[280,1403,1404,1407,1408,1410,1411,1413],{},[144,1405,1406],{},"NODE_TLS_REJECT_UNAUTHORIZED=0"," in ",[144,1409,1205],{},", compose, Dockerfile, ",[144,1412,1209],{}," scripts and CI files (process-wide; the detail says so)",[280,1415,1416,708,1418,1421,1422,1424,1425,1407,1428,627,1430,1433],{},[144,1417,1324],{},[144,1419,1420],{},"UserKnownHostsFile=\u002Fdev\u002Fnull"," in shell scripts, CI files and in-repo ",[144,1423,1213],{},"; ",[144,1426,1427],{},"host_key_checking = False",[144,1429,1216],{},[144,1431,1432],{},"ANSIBLE_HOST_KEY_CHECKING=False"," (heuristic)",[141,1435,1436,1437,1439,1440,1443,1444,1447,1448,1451,1452,1455,1456,1459,1460,1463,1464,1467,1468,1471,1472,1475,1476,321,1479,1482,1483,1486,1487,1490],{},"SSH host keys are reported under ",[144,1438,520],{}," with protocol ",[144,1441,1442],{},"ssh"," and a detail that says \"SSH host key\"; TLS hits have the\nprotocol of the system they resolve to (",[144,1445,1446],{},"https",", ...) or ",[144,1449,1450],{},"tls",". A hit inside a call that is already a ",[144,1453,1454],{},"CONNECTS_TO"," edge\n(",[144,1457,1458],{},"cg external",") or an ",[144,1461,1462],{},"HTTP_CALLS"," edge (",[144,1465,1466],{},"cg api-calls",") also puts ",[144,1469,1470],{},"tls_verify: false"," on that edge and its external\nsystem, and both commands print ",[144,1473,1474],{},"tls verify off",". Verification left on (",[144,1477,1478],{},"verify=True",[144,1480,1481],{},"rejectUnauthorized: true",",\n",[144,1484,1485],{},"StrictHostKeyChecking yes",", a pinned ",[144,1488,1489],{},"hostVerifier",") is not a hit. A line inside a comment is not a hit.",[555,1492,1494,1495,311],{"id":1493},"outbound-urls-from-request-input-ssrf","Outbound URLs from request input (",[144,1496,547],{},[141,1498,1499,1500,1503,1504,708,1507,708,1510,1482,1513,627,1516,1519,1520,1522],{},"An index-time pass (",[144,1501,1502],{},"cg_code_graph\u002Fssrf_input.py",") walks each non-test function: it collects request-input sources,\nfollows local variables assigned from them (also template \u002F f-string \u002F concatenation \u002F ",[144,1505,1506],{},"sprintf",[144,1508,1509],{},".format",[144,1511,1512],{},"%",[144,1514,1515],{},"new URL(x)",[144,1517,1518],{},"urljoin","), and looks at the URL argument of outbound calls and DNS lookups. ",[144,1521,146],{}," only reads the\nresult; nothing is rescanned.",[256,1524,1525,1540],{},[259,1526,1527],{},[262,1528,1529,1531,1534,1537],{},[265,1530,1226],{},[265,1532,1533],{},"request input",[265,1535,1536],{},"outbound sinks",[265,1538,1539],{},"DNS lookups",[275,1541,1542,1629,1704],{},[262,1543,1544,1546,1604,1618],{},[280,1545,1274],{},[280,1547,1548,708,1551,708,1554,708,1557,708,1560,321,1563,321,1566,321,1569,321,1572,708,1575,708,1578,321,1581,321,1584,1587,1588,708,1591,708,1594,708,1597,1600,1601],{},[144,1549,1550],{},"req.query",[144,1552,1553],{},"body",[144,1555,1556],{},"params",[144,1558,1559],{},"headers",[144,1561,1562],{},"cookies",[144,1564,1565],{},"getQuery(event)",[144,1567,1568],{},"readBody(event)",[144,1570,1571],{},"getRouterParam(event, 'k')",[144,1573,1574],{},"c.req.query()",[144,1576,1577],{},"param()",[144,1579,1580],{},"json()",[144,1582,1583],{},"request.nextUrl.searchParams.get",[144,1585,1586],{},"await request.json()",", Nest ",[144,1589,1590],{},"@Query()",[144,1592,1593],{},"@Body()",[144,1595,1596],{},"@Param()",[144,1598,1599],{},"@Headers()"," parameters, Elysia ",[144,1602,1603],{},"{ query, body, params }",[280,1605,1606,321,1609,1612,1613,708,1616],{},[144,1607,1608],{},"fetch",[144,1610,1611],{},"$fetch",", axios, got, ky, needle, superagent, undici, node ",[144,1614,1615],{},"http",[144,1617,1446],{},[280,1619,1620,321,1623,321,1626],{},[144,1621,1622],{},"dns.lookup",[144,1624,1625],{},"dns.resolve*",[144,1627,1628],{},"dns.promises.*",[262,1630,1631,1633,1677,1696],{},[280,1632,79],{},[280,1634,1635,708,1638,708,1641,708,1644,708,1647,708,1650,708,1652,708,1655,708,1658,321,1661,321,1664,708,1667,708,1670,708,1673,1676],{},[144,1636,1637],{},"$request->input",[144,1639,1640],{},"query",[144,1642,1643],{},"get",[144,1645,1646],{},"post",[144,1648,1649],{},"json",[144,1651,696],{},[144,1653,1654],{},"route",[144,1656,1657],{},"header",[144,1659,1660],{},"cookie",[144,1662,1663],{},"request('k')",[144,1665,1666],{},"$_GET",[144,1668,1669],{},"$_POST",[144,1671,1672],{},"$_REQUEST",[144,1674,1675],{},"$_COOKIE",", route parameters of controller actions",[280,1678,1679,1680,1683,1684,708,1687,321,1690,321,1693],{},"Laravel ",[144,1681,1682],{},"Http::",", Guzzle clients, ",[144,1685,1686],{},"curl_init",[144,1688,1689],{},"CURLOPT_URL",[144,1691,1692],{},"file_get_contents",[144,1694,1695],{},"fopen",[280,1697,1698,321,1701],{},[144,1699,1700],{},"gethostbyname",[144,1702,1703],{},"dns_get_record",[262,1705,1706,1708,1743,1752],{},[280,1707,95],{},[280,1709,1710,1711,708,1714,708,1717,708,1720,708,1723,708,1725,1728,1729,708,1732,708,1735,708,1737,708,1740,1742],{},"Django ",[144,1712,1713],{},"request.GET",[144,1715,1716],{},"POST",[144,1718,1719],{},"data",[144,1721,1722],{},"query_params",[144,1724,1559],{},[144,1726,1727],{},"META",", Flask ",[144,1730,1731],{},"request.args",[144,1733,1734],{},"form",[144,1736,1649],{},[144,1738,1739],{},"values",[144,1741,1559],{},", FastAPI \u002F Starlette handler parameters, Django URL kwargs",[280,1744,1745,1746,611,1749],{},"requests, httpx, aiohttp (module calls and sessions \u002F clients), ",[144,1747,1748],{},"urllib",[144,1750,1751],{},"urlopen",[280,1753,1754,321,1757],{},[144,1755,1756],{},"socket.gethostbyname",[144,1758,1759],{},"socket.getaddrinfo",[141,1761,1762,1763,1766,1767,1770,1771,321,1773,321,1775,1482,1778,321,1780,635,1783,1424,1785,1788,1789,1792,1793,1796,1797,1770,1800,1803,1804,1807,1808,1811,1812,1815,1816,321,1819,321,1822,1825,1826,708,1829,708,1832,1835,1836,1839],{},"Each use is recorded as ",[144,1764,1765],{},"url_from_input = {source, key, part, via, checked}",": ",[144,1768,1769],{},"source"," is ",[144,1772,1640],{},[144,1774,1553],{},[144,1776,1777],{},"param",[144,1779,1657],{},[144,1781,1782],{},"path",[144,1784,1660],{},[144,1786,1787],{},"key"," is the input name (",[144,1790,1791],{},"*"," when the whole container is used); ",[144,1794,1795],{},"part"," is what the input\ncontrols; ",[144,1798,1799],{},"via",[144,1801,1802],{},"direct"," (same function) or ",[144,1805,1806],{},"helper"," (one level: a handler passes the value to a function whose URL\nuses that parameter, found through the ",[144,1809,1810],{},"CALLS"," edge); ",[144,1813,1814],{},"checked"," is true when a comparison with a constant allow-list\n(",[144,1817,1818],{},"in ALLOWED_HOSTS",[144,1820,1821],{},"ALLOWED.has(u.host)",[144,1823,1824],{},"in_array($host, self::ALLOWED)",") or a ",[144,1827,1828],{},"startsWith",[144,1830,1831],{},"startswith",[144,1833,1834],{},"str_starts_with"," test against a literal ",[144,1837,1838],{},"https:\u002F\u002F..."," prefix precedes the call. Only names are stored,\nnever a value from a request.",[256,1841,1842,1856],{},[259,1843,1844],{},[262,1845,1846,1850,1853],{},[265,1847,1848],{},[144,1849,1795],{},[265,1851,1852],{},"meaning",[265,1854,1855],{},"reported",[275,1857,1858,1881,1897,1918],{},[262,1859,1860,1865,1878],{},[280,1861,1862],{},[144,1863,1864],{},"url",[280,1866,1867,1868,708,1870,1873,1874,1877],{},"the whole URL, a template that starts with the input, or ",[144,1869,1515],{},[144,1871,1872],{},"urljoin(base, x)"," (an absolute ",[144,1875,1876],{},"x"," replaces the host)",[280,1879,1880],{},"yes",[262,1882,1883,1888,1895],{},[280,1884,1885],{},[144,1886,1887],{},"host",[280,1889,1890,1891,1894],{},"the host in ",[144,1892,1893],{},"https:\u002F\u002F${x}\u002F..."," or a DNS lookup name",[280,1896,1880],{},[262,1898,1899,1905,1915],{},[280,1900,1901,321,1903],{},[144,1902,1782],{},[144,1904,1640],{},[280,1906,1907,1908,321,1911,1914],{},"input after a fixed host (",[144,1909,1910],{},"https:\u002F\u002Fapi.example\u002F${id}",[144,1912,1913],{},"...?q="," + x)",[280,1916,1917],{},"no, recorded only",[262,1919,1920,1925,1928],{},[280,1921,1922,1923],{},"any, with ",[144,1924,1814],{},[280,1926,1927],{},"an allow-list check precedes the call",[280,1929,1917],{},[141,1931,1932,1933,708,1935,1937,1938,1940,1941,1943,1944,1424,1947,627,1950,1953,1954,1957,1958,635,1960,1963,1964,611,1967,1970,1971,1973,1974,1976,1977,321,1980,1482,1983,1985,1986,153],{},"The fact sits on the ",[144,1934,1462],{},[144,1936,1454],{}," edge of the call when the call has one (",[144,1939,1466],{}," and\n",[144,1942,1458],{}," print ",[144,1945,1946],{},"url from input (query.url, url)",[144,1948,1949],{},"host checked",[144,1951,1952],{},"via helper"," are added when they apply). A call\nwith no edge keeps the fact on the enclosing function, as an ",[144,1955,1956],{},"insecure_transport"," fact of kind ",[144,1959,547],{},[144,1961,1962],{},"dns-input",".\nThe finding has ",[144,1965,1966],{},"confidence",[144,1968,1969],{},"resolved"," for a direct flow and ",[144,1972,444],{}," through a helper, and its entry points are the\nroutes that reach the function. A DNS lookup of input is an ",[144,1975,547],{}," finding with the detail \"DNS lookup of request input\"\nat medium severity (it resolves a chosen name but sends no request body). ",[144,1978,1979],{},"--finding ssrf",[144,1981,1982],{},"--fail-on ssrf",[144,1984,1176],{},", SARIF and the MCP tool need no special handling. Test files and test callers are skipped and counted\n(",[144,1987,1197],{},[155,1989,1994],{"className":1990,"code":1992,"language":1993},[1991],"language-text","$ cg surface --db out\u002Fgraph.db --finding ssrf\n== ssrf (3) - Outbound request to a host chosen by request input ==\n  dns\n    MEDIUM [resolved] function:shop.views.mirror\n      DNS lookup of request input (query.host)\n      at shop\u002Fviews.py:36\n      reached from http_route(1)\n  http\n    HIGH [heuristic] function:shop.feeds.pull_feed\n      the whole outbound URL comes from request input (body.feed) via helper, passed by shop.views.import_feed\n      at shop\u002Ffeeds.py:5\n      reached from http_route(1)\n    HIGH [resolved] function:shop.views.cover\n      the whole outbound URL comes from request input (query.url)\n      at shop\u002Fviews.py:14\n      reached from http_route(1)\n\n$ cg external --db out\u002Fgraph.db\nexternal:https:covers.bookstore.test:443  [exact]\n    \u003C- function:src\u002Fapp.ts#coverByIsbn @ src\u002Fapp.ts:15  (http)  [url from input (param.isbn, path)]\n","text",[144,1995,1992],{"__ignoreMap":160},[555,1997,1999,2000,2002,2003,311],{"id":1998},"insecure-grpc-plaintext-protocol-grpc","Insecure gRPC (",[144,2001,415],{},", protocol ",[144,2004,448],{},[141,2006,2007,2008,2010,2011,708,2014,2017,2018,708,2021,2024,2025,2028,2029,708,2032,2035,2036,2039,2040,2043,2044,2047,2048,321,2051,2054,2055,1217],{},"A second producer under the ",[144,2009,415],{}," name: Python ",[144,2012,2013],{},"grpc.insecure_channel",[144,2015,2016],{},"add_insecure_port",", TypeScript\n",[144,2019,2020],{},"credentials.createInsecure()",[144,2022,2023],{},"ServerCredentials.createInsecure()",", Java and Kotlin ",[144,2026,2027],{},"ManagedChannelBuilder...usePlaintext()",",\nGo ",[144,2030,2031],{},"grpc.WithInsecure()",[144,2033,2034],{},"insecure.NewCredentials()",", Rust tonic ",[144,2037,2038],{},"Channel::from_static(\"http:\u002F\u002F...\")",", Dart\n",[144,2041,2042],{},"ChannelCredentials.insecure()",". A ",[144,2045,2046],{},"unix:"," target (already a local socket) and a loopback target are not findings; the\nindex counts them (",[144,2049,2050],{},"grpc_unix",[144,2052,2053],{},"grpc_loopback","). A target that is not a literal is reported at ",[144,2056,444],{},[555,2058,2060,2061,311],{"id":2059},"ipc-exposure-ipc-exposed","IPC exposure (",[144,2062,532],{},[256,2064,2065,2077],{},[259,2066,2067],{},[262,2068,2069,2072,2075],{},[265,2070,2071],{},"trigger",[265,2073,2074],{},"protocol",[265,2076,1769],{},[275,2078,2079,2104,2136,2167,2193],{},[262,2080,2081,2086,2091],{},[280,2082,2083],{},[144,2084,2085],{},"window.postMessage(msg, '*')",[280,2087,2088],{},[144,2089,2090],{},"postmessage",[280,2092,2093,2094,2097,2098,2101,2102,311],{},"the ",[144,2095,2096],{},"SENDS_TO"," edge's ",[144,2099,2100],{},"target_origin: \"*\""," (",[149,2103,91],{"href":92},[262,2105,2106,2122,2127],{},[280,2107,2108,2109,2112,2113,321,2116,321,2119,311],{},"a Unix socket whose recorded ",[144,2110,2111],{},"mode"," is world-writable (",[144,2114,2115],{},"0o666",[144,2117,2118],{},"0o777",[144,2120,2121],{},"o+w",[280,2123,2124],{},[144,2125,2126],{},"unix",[280,2128,2129,2132,2133],{},[144,2130,2131],{},"attrs.mode"," on ",[144,2134,2135],{},"endpoint:unix:\u003Cpath>",[262,2137,2138,2159,2164],{},[280,2139,2140,611,2142,2145,2146,2101,2149,321,2152,2155,2156],{},[144,2141,938],{},[144,2143,2144],{},"externally_connectable"," with wildcard ",[144,2147,2148],{},"matches",[144,2150,2151],{},"*:\u002F\u002F*\u002F*",[144,2153,2154],{},"\u003Call_urls>",") or ",[144,2157,2158],{},"ids: [\"*\"]",[280,2160,2161],{},[144,2162,2163],{},"extension",[280,2165,2166],{},"index-time pass",[262,2168,2169,2187,2191],{},[280,2170,2171,708,2174,2177,2178,708,2181,708,2184,2186],{},[144,2172,2173],{},"runtime.onConnectExternal",[144,2175,2176],{},"onMessageExternal"," listener with no ",[144,2179,2180],{},"sender.id",[144,2182,2183],{},"origin",[144,2185,1864],{}," check",[280,2188,2189],{},[144,2190,2163],{},[280,2192,2166],{},[262,2194,2195,2211,2216],{},[280,2196,2197,2198,2201,2202,321,2205,635,2208],{},"Electron ",[144,2199,2200],{},"webPreferences"," with ",[144,2203,2204],{},"nodeIntegration: true",[144,2206,2207],{},"contextIsolation: false",[144,2209,2210],{},"webSecurity: false",[280,2212,2213],{},[144,2214,2215],{},"electron",[280,2217,2166],{},[141,2219,2220,2221,2223,2224,2226,2227,2231,2232,2235],{},"A message listener without an origin check, an ",[144,2222,2176],{}," listener without a sender check, and an exported\nAndroid component without a permission are ",[144,2225,341],{}," findings already (",[149,2228,2230],{"href":2229},"\u002Fdocs\u002Flimitations#link-payload-and-guards","Link, payload, and guards","):\neach item is reported under one finding only. A sender with a concrete origin, a ",[144,2233,2234],{},"0o600"," socket, a pinned manifest and an\nElectron window with the secure defaults are not findings.",[141,2237,2238,2239,321,2241,321,2243,321,2245,321,2247,321,2250,321,2253,321,2255,1482,2258,627,2261,830,2264,2266,2267,321,2270,321,2273,2276,2277,2280,2281,2284,2285,2288],{},"Every finding carries ",[144,2240,359],{},[144,2242,365],{},[144,2244,1966],{},[144,2246,2074],{},[144,2248,2249],{},"node",[144,2251,2252],{},"file",[144,2254,166],{},[144,2256,2257],{},"entry_points",[144,2259,2260],{},"detail",[144,2262,2263],{},"fingerprint",[144,2265,2257],{}," lists the entry kinds that reach the finding (",[144,2268,2269],{},"kind",[144,2271,2272],{},"count",[144,2274,2275],{},"sample","; at\nmost five kinds, the total in ",[144,2278,2279],{},"entry_point_total","). On a combined graph (",[144,2282,2283],{},"cg link",") each finding also names its ",[144,2286,2287],{},"repo",".\nThe fingerprint hashes finding, node id and file, never the line, so it survives edits above the finding.",[141,2290,2291,2292,2294,2295,2298,2299,2302,2303,2305],{},"An ",[144,2293,341],{}," finding as it prints after part 2d, on the ",[144,2296,2297],{},"tests\u002Fsurface_fixture"," apps (the ",[144,2300,2301],{},"\u002Fhealth"," route is ",[144,2304,332],{}," and not listed; the second route reads only):",[155,2307,2310],{"className":2308,"code":2309,"language":1993,"meta":160},[1991],"$ cg surface --db out\u002Fgraph.db --finding unguarded\nattack surface: 2 finding(s) (1 medium, 1 low)\ninbound surface: 9 item(s) (1 guarded, 1 public, 1 secret-checked, 4 unchecked, 2 unguarded); outbound surface: 6 system(s)\n\n== unguarded (2) - Inbound handler without a guard ==\n  http\n    MEDIUM [resolved] route:ANY \u002Fadmin\u002Fpurge\u002F\n      ANY \u002Fadmin\u002Fpurge\u002F has no auth guard and reaches a write\n      at py\u002Fbookshop\u002Furls.py:7\n      reached from http_route(1)\n      fingerprint 4f95d4747c5550f1\n    LOW [resolved] route:ANY \u002Fhooks\u002Fgithub-open\u002F\n      ANY \u002Fhooks\u002Fgithub-open\u002F has no auth guard\n      at py\u002Fbookshop\u002Furls.py:9\n      reached from http_route(1)\n      fingerprint 36091b3f583debb1\n",[144,2311,2309],{"__ignoreMap":160},[141,2313,2314,2315,2317,2318,1217],{},"An unclear case adds its reason to the detail and has ",[144,2316,444],{}," confidence, for example ",[144,2319,2320],{},"ANY \u002Fgraphql\u002F has no auth guard (unclear: GraphQL endpoint: access is checked per field)",[155,2322,2325],{"className":2323,"code":2324,"language":1993,"meta":160},[1991],"$ cg surface --db out\u002Fgraph.db --finding hardcoded\nattack surface: 2 finding(s) (2 high)\ninbound surface: 7 item(s) (1 guarded, 1 secret-checked, 2 unchecked, 3 unguarded); outbound surface: 4 system(s)\n\n== hardcoded (2) - Hardcoded credential ==\n  postgres\n    HIGH [exact] external:postgres:db.bookstore.example:5432\n      literal password in code or configuration for db.bookstore.example:5432 (value not shown)\n      at bookshop\u002Fsettings.py:6\n      reached from no entry point\n      fingerprint 75454517a93c3363\n  saas\n    HIGH [exact] external:saas:postmark\n      literal key in code or configuration for postmark (value not shown)\n      at bookshop\u002Fnotify.py:6\n      reached from http_route(1)\n      fingerprint 3412ee3e89b46e48\n",[144,2326,2324],{"__ignoreMap":160},[141,2328,2329],{},"Disabled verification, insecure gRPC and IPC exposure on the test fixtures:",[155,2331,2334],{"className":2332,"code":2333,"language":1993,"meta":160},[1991],"$ cg surface --db out\u002Fgraph.db --finding tls-off\n== tls-off (8) - TLS or SSH host verification disabled ==\n  ssh\n    HIGH [exact] function:bookshop.catalog.push_stock\n      SSH host key not checked (AutoAddPolicy \u002F WarningPolicy)\n      at bookshop\u002Fcatalog.py:40\n      reached from no entry point\n      fingerprint 765683799095b51d\n  tls\n    HIGH [resolved] function:bookshop.catalog.sync_catalog\n      certificate verification is switched off (verify=False)\n      at bookshop\u002Fcatalog.py:9\n      reached from no entry point\n      fingerprint 4a56ab0842be36a3\n\n$ cg surface --db out\u002Fgraph.db --finding plaintext --protocol grpc\n== plaintext (2) - Plaintext protocol ==\n  grpc\n    MEDIUM [resolved] function:bookshop.catalog.inventory_channel\n      client channel without TLS: inventory.bookstore.example:50051\n      at bookshop\u002Fcatalog.py:54\n      reached from no entry point\n      fingerprint 718a42ac53b9e2ce\n\n$ cg surface --db out\u002Fgraph.db --finding ipc-exposed\n== ipc-exposed (8) - IPC endpoint open to untrusted callers ==\n  extension\n    MEDIUM [exact] file:config:extension-open\u002Fmanifest.json\n      externally_connectable.matches accepts any web page (*:\u002F\u002F*\u002F*)\n      at extension-open\u002Fmanifest.json:6\n      reached from message_handler(1)\n      fingerprint f9f6fad48a93744a\n  unix\n    MEDIUM [exact] endpoint:unix:\u002Frun\u002Fbookshop\u002Fagent-open.sock\n      Unix socket \u002Frun\u002Fbookshop\u002Fagent-open.sock is chmod 0o666: any local user can connect\n      at agent\u002Fsock.py:10\n      reached from message_handler(1)\n      fingerprint 113ead5ceff1f789\n",[144,2335,2333],{"__ignoreMap":160},[251,2337,2339],{"id":2338},"options","Options",[256,2341,2342,2351],{},[259,2343,2344],{},[262,2345,2346,2349],{},[265,2347,2348],{},"option",[265,2350,1852],{},[275,2352,2353,2364,2394,2404,2421,2437,2447,2457,2468],{},[262,2354,2355,2361],{},[280,2356,2357,708,2359],{},[144,2358,284],{},[144,2360,301],{},[280,2362,2363],{},"list that surface; findings are limited to its direction",[262,2365,2366,2371],{},[280,2367,2368],{},[144,2369,2370],{},"--protocol P",[280,2372,2373,2374,321,2376,321,2379,321,2381,321,2384,321,2387,321,2390,2393],{},"one protocol (",[144,2375,1615],{},[144,2377,2378],{},"ws",[144,2380,448],{},[144,2382,2383],{},"tcp",[144,2385,2386],{},"redis",[144,2388,2389],{},"postgres",[144,2391,2392],{},"saas",", ...)",[262,2395,2396,2401],{},[280,2397,2398],{},[144,2399,2400],{},"--finding F",[280,2402,2403],{},"one finding type",[262,2405,2406,2411],{},[280,2407,2408],{},[144,2409,2410],{},"--min-confidence resolved",[280,2412,2413,2414,2416,2417,2420],{},"drop ",[144,2415,444],{}," findings and items (",[144,2418,2419],{},"exact"," keeps only exact)",[262,2422,2423,2428],{},[280,2424,2425],{},[144,2426,2427],{},"--format text|json|sarif",[280,2429,2430,2432,2433,2436],{},[144,2431,1649],{}," is the full structure; ",[144,2434,2435],{},"sarif"," is SARIF 2.1.0",[262,2438,2439,2444],{},[280,2440,2441],{},[144,2442,2443],{},"--fail-on f1,f2",[280,2445,2446],{},"exit 1 when any listed finding remains after ignores",[262,2448,2449,2454],{},[280,2450,2451],{},[144,2452,2453],{},"--max-items N",[280,2455,2456],{},"cap on listed findings and items (default 200); totals stay in the summary",[262,2458,2459,2463],{},[280,2460,2461],{},[144,2462,487],{},[280,2464,2465,2467],{},[144,2466,341],{}," counts route guards only: no framework defaults, no handler checks, no public-route table, medium severity",[262,2469,2470,2475],{},[280,2471,2472],{},[144,2473,2474],{},"--show-ignored",[280,2476,2477,2478,2480],{},"list the findings ",[144,2479,1176],{}," accepted",[141,2482,2483,2484,338,2487,2490,2491,2494,2495,2498,2499,2502,2503,2506,2507,1217],{},"SARIF has one rule per finding type (",[144,2485,2486],{},"cg.surface.\u003Cfinding>",[144,2488,2489],{},"level"," from severity (high ",[144,2492,2493],{},"error",", medium ",[144,2496,2497],{},"warning","),\nrepo-relative locations, and the fingerprint in ",[144,2500,2501],{},"partialFingerprints",". Ignored findings appear as results with a\n",[144,2504,2505],{},"suppressions"," entry only with ",[144,2508,2474],{},[251,2510,2512],{"id":2511},"gates","Gates",[141,2514,2515,2518,2519,627,2522,1424,2525,321,2528,627,2530,2532],{},[144,2516,2517],{},"--fail-on"," exits 1 when a listed finding remains, 0 otherwise, 2 for an unknown name. It judges every finding type\nafter ignores, ",[144,2520,2521],{},"--protocol",[144,2523,2524],{},"--min-confidence",[144,2526,2527],{},"--finding",[144,2529,284],{},[144,2531,301],{}," only change what is printed.",[155,2534,2538],{"className":2535,"code":2536,"language":2537,"meta":160,"style":160},"language-yaml shiki shiki-themes cg-light cg-light cg-dark","# GitHub Actions\n- run: cg index . --db out\u002Fgraph.db\n- run: cg surface --db out\u002Fgraph.db --format sarif > cg.sarif && cg surface --db out\u002Fgraph.db --fail-on hardcoded,unverified\n- uses: github\u002Fcodeql-action\u002Fupload-sarif@v3\n  if: always()\n  with: { sarif_file: cg.sarif }\n","yaml",[144,2539,2540,2545,2560,2571,2583,2594],{"__ignoreMap":160},[164,2541,2542],{"class":166,"line":167},[164,2543,2544],{"class":184},"# GitHub Actions\n",[164,2546,2547,2550,2554,2557],{"class":166,"line":188},[164,2548,2549],{"class":225},"-",[164,2551,2553],{"class":2552},"s4LKg"," run",[164,2555,2556],{"class":225},":",[164,2558,2559],{"class":174}," cg index . --db out\u002Fgraph.db\n",[164,2561,2562,2564,2566,2568],{"class":166,"line":208},[164,2563,2549],{"class":225},[164,2565,2553],{"class":2552},[164,2567,2556],{"class":225},[164,2569,2570],{"class":174}," cg surface --db out\u002Fgraph.db --format sarif > cg.sarif && cg surface --db out\u002Fgraph.db --fail-on hardcoded,unverified\n",[164,2572,2573,2575,2578,2580],{"class":166,"line":232},[164,2574,2549],{"class":225},[164,2576,2577],{"class":2552}," uses",[164,2579,2556],{"class":225},[164,2581,2582],{"class":174}," github\u002Fcodeql-action\u002Fupload-sarif@v3\n",[164,2584,2586,2589,2591],{"class":166,"line":2585},5,[164,2587,2588],{"class":2552},"  if",[164,2590,2556],{"class":225},[164,2592,2593],{"class":174}," always()\n",[164,2595,2597,2600,2602,2605,2608,2610,2613],{"class":166,"line":2596},6,[164,2598,2599],{"class":2552},"  with",[164,2601,2556],{"class":225},[164,2603,2604],{"class":225}," {",[164,2606,2607],{"class":2552}," sarif_file",[164,2609,2556],{"class":225},[164,2611,2612],{"class":174}," cg.sarif",[164,2614,2615],{"class":225}," }\n",[251,2617,2619],{"id":2618},"ignores","Ignores",[141,2621,2622,2623,2626,2627,2630],{},"An accepted risk goes in ",[144,2624,2625],{},".cg.yaml",". The reason is required; ",[144,2628,2629],{},"cg config validate"," rejects an entry without one.",[155,2632,2634],{"className":2535,"code":2633,"language":2537,"meta":160,"style":160},"surface:\n  ignore:\n    - {finding: unguarded, path: \"app\u002FHttp\u002FControllers\u002FHealthController.php\", reason: public health probe}\n    - {finding: plaintext, id: \"external:redis:cache:6379\", reason: compose-internal network}\n    - {finding: hardcoded, fingerprint: 75454517a93c3363, reason: rotated test key}\n",[144,2635,2636,2643,2650,2694,2732],{"__ignoreMap":160},[164,2637,2638,2640],{"class":166,"line":167},[164,2639,267],{"class":2552},[164,2641,2642],{"class":225},":\n",[164,2644,2645,2648],{"class":166,"line":188},[164,2646,2647],{"class":2552},"  ignore",[164,2649,2642],{"class":225},[164,2651,2652,2655,2657,2659,2661,2664,2667,2670,2672,2675,2678,2681,2683,2686,2688,2691],{"class":166,"line":208},[164,2653,2654],{"class":225},"    -",[164,2656,2604],{"class":225},[164,2658,359],{"class":2552},[164,2660,2556],{"class":225},[164,2662,2663],{"class":174}," unguarded",[164,2665,2666],{"class":225},",",[164,2668,2669],{"class":2552}," path",[164,2671,2556],{"class":225},[164,2673,2674],{"class":225}," \"",[164,2676,2677],{"class":174},"app\u002FHttp\u002FControllers\u002FHealthController.php",[164,2679,2680],{"class":225},"\"",[164,2682,2666],{"class":225},[164,2684,2685],{"class":2552}," reason",[164,2687,2556],{"class":225},[164,2689,2690],{"class":174}," public health probe",[164,2692,2693],{"class":225},"}\n",[164,2695,2696,2698,2700,2702,2704,2707,2709,2712,2714,2716,2719,2721,2723,2725,2727,2730],{"class":166,"line":232},[164,2697,2654],{"class":225},[164,2699,2604],{"class":225},[164,2701,359],{"class":2552},[164,2703,2556],{"class":225},[164,2705,2706],{"class":174}," plaintext",[164,2708,2666],{"class":225},[164,2710,2711],{"class":2552}," id",[164,2713,2556],{"class":225},[164,2715,2674],{"class":225},[164,2717,2718],{"class":174},"external:redis:cache:6379",[164,2720,2680],{"class":225},[164,2722,2666],{"class":225},[164,2724,2685],{"class":2552},[164,2726,2556],{"class":225},[164,2728,2729],{"class":174}," compose-internal network",[164,2731,2693],{"class":225},[164,2733,2734,2736,2738,2740,2742,2745,2747,2750,2752,2755,2757,2759,2761,2764],{"class":166,"line":2585},[164,2735,2654],{"class":225},[164,2737,2604],{"class":225},[164,2739,359],{"class":2552},[164,2741,2556],{"class":225},[164,2743,2744],{"class":174}," hardcoded",[164,2746,2666],{"class":225},[164,2748,2749],{"class":2552}," fingerprint",[164,2751,2556],{"class":225},[164,2753,2754],{"class":174}," 75454517a93c3363",[164,2756,2666],{"class":225},[164,2758,2685],{"class":2552},[164,2760,2556],{"class":225},[164,2762,2763],{"class":174}," rotated test key",[164,2765,2693],{"class":225},[141,2767,2768,2770,2771,2773,2774,2777,2778,2780,2781,2783,2784,2786],{},[144,2769,359],{}," is a finding type, plus one of ",[144,2772,1782],{}," (a file, a directory or a glob, repo-relative), ",[144,2775,2776],{},"id"," (a node id or glob)\nor ",[144,2779,2263],{},". Every key given must match. The file is read again from the indexed root on each run, so an ignore\napplies without re-indexing. Ignored findings are counted in the summary and listed with ",[144,2782,2474],{},". On a\ncombined graph an ignore applies to the findings of the repo whose ",[144,2785,2625],{}," holds it.",[251,2788,2790],{"id":2789},"mcp","MCP",[141,2792,2793,2794,2797,2798,321,2801,321,2804,321,2806,321,2808,321,2811,1482,2814,627,2817,2820,2821,2823,2824,2827,2828,153],{},"The ",[144,2795,2796],{},"attack_surface"," tool takes ",[144,2799,2800],{},"inbound",[144,2802,2803],{},"outbound",[144,2805,2074],{},[144,2807,359],{},[144,2809,2810],{},"min_confidence",[144,2812,2813],{},"strict",[144,2815,2816],{},"show_ignored",[144,2818,2819],{},"max_items",". The reply is the text report; the structured reply has the full result under\n",[144,2822,267],{}," next to the ",[144,2825,2826],{},"completeness"," object (",[149,2829,62],{"href":63},[251,2831,152],{"id":2832},"no-secret-values-in-the-index",[141,2834,2835,2836,321,2838,321,2840,321,2843,321,2846,2849,2850,2853,2854,2857,2858,2861,2862,2865,2866,2869,2870,2872,2873,2876,2877,321,2880,2882,2883,321,2885,321,2888,321,2891,321,2894,321,2896,635,2898,2900,2901,321,2903,321,2906,321,2909,321,2912,321,2915,321,2918,321,2921,321,2924,321,2927,321,2930,321,2933,321,2936,2939,2940,708,2943,2946,2947,635,2949,2951,2952,321,2955,321,2958,321,2961,321,2964,321,2966,321,2969,321,2972,321,2975,321,2977,321,2980,321,2982,321,2985,321,2988,321,2991,321,2994,321,2997,321,3000,2101,3003,321,3006,321,3009,3012,3013,3016,3017,627,3020,3023,3024,3027,3028,321,3031,627,3034,3037,3038,321,3041,321,3044,627,3047,3050,3051,627,3054,3057,3058,3061,3062,3065,3066,3068,3069,3072,3073,3076,3077,3080,3081,3084,3085,3088],{},"A credential literal is never kept in the graph database. At index time a literal under a key that looks like a\ncredential (",[144,2837,391],{},[144,2839,397],{},[144,2841,2842],{},"api_key",[144,2844,2845],{},"passphrase",[144,2847,2848],{},"dsn",", a token or key value, ...) is stored as ",[144,2851,2852],{},"redacted:hmac:\u003Cfirst 8 hex of HMAC-SHA-256(salt, value)>",": it shows that a literal exists and tells two values apart\nwithin the graph, and it cannot be reversed. The salt is ",[144,2855,2856],{},"secrets.token_hex(16)",", created with the database and stored in\nthe ",[144,2859,2860],{},"meta"," table under ",[144,2863,2864],{},"redact_salt","; re-indexing and ",[144,2867,2868],{},"cg refresh"," keep it, a new database gets a new one. The salt is not\nsecret from someone who holds the database, but a marker from a leaked screenshot, log or SARIF file cannot be matched\nagainst a table of common passwords without it, and the same password in two graphs gives two markers. Fingerprints are\ncomparable inside one graph only: a ",[144,2871,2283],{}," combined graph keeps the markers of its sources and has no salt of its own,\nand comparing a secret across repos is not supported. Indexes made before part 2d hold ",[144,2874,2875],{},"redacted:sha256:","; both prefixes\ncount as redacted, and re-indexing replaces the old form. Only the last word of the key (the last dotted segment, split on ",[144,2878,2879],{},"_",[144,2881,2549],{}," and camelCase) decides: it is a credential when it is ",[144,2884,391],{},[144,2886,2887],{},"passwd",[144,2889,2890],{},"pwd",[144,2892,2893],{},"pass",[144,2895,2845],{},[144,2897,397],{},[144,2899,2848],{},", when the name ends in an explicit compound (",[144,2902,2842],{},[144,2904,2905],{},"access_key",[144,2907,2908],{},"secret_key",[144,2910,2911],{},"private_key",[144,2913,2914],{},"signing_key",[144,2916,2917],{},"encryption_key",[144,2919,2920],{},"client_secret",[144,2922,2923],{},"webhook_secret",[144,2925,2926],{},"auth_token",[144,2928,2929],{},"api_token",[144,2931,2932],{},"access_token",[144,2934,2935],{},"refresh_token",[144,2937,2938],{},"bearer_token","), or for Laravel's ",[144,2941,2942],{},"app.key",[144,2944,2945],{},"APP_KEY",". A bare ",[144,2948,1787],{},[144,2950,394],{}," as last word is redacted by default, as before. Its value is kept only when the word before it (a dotted segment counts) is one of ",[144,2953,2954],{},"primary",[144,2956,2957],{},"foreign",[144,2959,2960],{},"sort",[144,2962,2963],{},"partition",[144,2965,1654],{},[144,2967,2968],{},"cache",[144,2970,2971],{},"idempotency",[144,2973,2974],{},"remember",[144,2976,882],{},[144,2978,2979],{},"xsrf",[144,2981,332],{},[144,2983,2984],{},"i18n",[144,2986,2987],{},"translation",[144,2989,2990],{},"lookup",[144,2992,2993],{},"unique",[144,2995,2996],{},"index",[144,2998,2999],{},"storage",[144,3001,3002],{},"prefix",[144,3004,3005],{},"primary_key",[144,3007,3008],{},"cache.key",[144,3010,3011],{},"remember_token","), and the value never decides, because a token can have any shape (lowercase words, dotted, ",[144,3014,3015],{},"key-"," prefixed). ",[144,3018,3019],{},"services.ledger.token",[144,3021,3022],{},"LEDGER_TOKEN"," therefore stay redacted for any value that has a digit, an upper-case letter or a ",[144,3025,3026],{},"base64:"," form. ",[144,3029,3030],{},"AUTH_GUARD",[144,3032,3033],{},"AUTH_PASSWORD_BROKER",[144,3035,3036],{},"try_it_credentials_policy"," are not credentials, and ",[144,3039,3040],{},"*_PUBLIC_KEY",[144,3042,3043],{},"*_KEY_ID",[144,3045,3046],{},"*_KEY_PATH",[144,3048,3049],{},"*_KEY_FILE"," never are. This covers the Laravel config ",[144,3052,3053],{},"value",[144,3055,3056],{},"env('X', 'default')"," default attrs. A password\ninside any ",[144,3059,3060],{},"scheme:\u002F\u002Fuser:password@host"," string in a node or edge attr gets the same marker, so the host stays\nreadable. Non-secret values (",[144,3063,3064],{},"'currency' => 'EUR'",", a hostname default) are stored as before, and ",[144,3067,374],{}," still\nsees a marker as a literal. ",[144,3070,3071],{},"cg node config:..."," and a direct database read show the marker, never the value. The\nexternal-system attrs of #42 parts 3a \u002F 3b and the Python and TypeScript config readers keep addresses, key names and\nlocations only, so they have nothing to redact; a test indexes the surface fixtures and fails if a ",[144,3074,3075],{},"SURF-"," marker\nappears in any table. The ",[144,3078,3079],{},"doc"," text of a node is swept as well, so an example DSN in a comment keeps its host and loses\nits password. A map keyed by source identifiers (",[144,3082,3083],{},"reexports",") is left alone: an exported class named ",[144,3086,3087],{},"ApiKey"," is a name.",[251,3090,3092],{"id":3091},"validation","Validation",[141,3094,3095,3096,830,3100,3102,3103,153],{},"Counts, sampled true \u002F false positives per finding type and recall on three intentionally vulnerable apps are in the\n",[149,3097,3099],{"href":3098},"\u002Fdocs\u002Fvalidation-log#attack-surface-47","validation log",[144,3101,341],{}," is the noisiest type on large apps, and still needs review after part 2d (",[149,3104,58],{"href":3105},"\u002Fdocs\u002Flimitations#attack-surface",[251,3107,3109],{"id":3108},"not-detected-yet","Not detected yet",[141,3111,3112,3113,3115],{},"Numeric validation limits are not produced by this version (",[149,3114,58],{"href":3105},"). New\ndetectors add a finding type; the shape above does not change.",[3117,3118,3119],"style",{},"html pre.shiki code .sZJo5, html code.shiki .sZJo5{--shiki-light:#65A30D;--shiki-default:#65A30D;--shiki-dark:#84CC16}html pre.shiki code .sha9Y, html code.shiki .sha9Y{--shiki-light:#9F8CFF;--shiki-default:#9F8CFF;--shiki-dark:#C4B5FD}html pre.shiki code .seS0s, html code.shiki .seS0s{--shiki-light:#71717A;--shiki-light-font-style:italic;--shiki-default:#71717A;--shiki-default-font-style:italic;--shiki-dark:#A1A1AA;--shiki-dark-font-style:italic}html pre.shiki code .sZ_TN, html code.shiki .sZ_TN{--shiki-light:#71717A;--shiki-default:#71717A;--shiki-dark:#A1A1AA}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4LKg, html code.shiki .s4LKg{--shiki-light:#4D7C0F;--shiki-default:#4D7C0F;--shiki-dark:#84CC16}",{"title":160,"searchDepth":232,"depth":188,"links":3121},[3122,3123,3136,3137,3138,3139,3140,3141,3142],{"id":253,"depth":188,"text":254},{"id":349,"depth":188,"text":350,"children":3124},[3125,3127,3128,3130,3132,3134],{"id":557,"depth":208,"text":3126},"How unguarded decides",{"id":820,"depth":208,"text":821},{"id":1180,"depth":208,"text":3129},"Disabled verification (tls-off)",{"id":1493,"depth":208,"text":3131},"Outbound URLs from request input (ssrf)",{"id":1998,"depth":208,"text":3133},"Insecure gRPC (plaintext, protocol grpc)",{"id":2059,"depth":208,"text":3135},"IPC exposure (ipc-exposed)",{"id":2338,"depth":188,"text":2339},{"id":2511,"depth":188,"text":2512},{"id":2618,"depth":188,"text":2619},{"id":2789,"depth":188,"text":2790},{"id":2832,"depth":188,"text":152},{"id":3091,"depth":188,"text":3092},{"id":3108,"depth":188,"text":3109},"cg surface reads facts the index already holds and answers three questions: what does the code expose, what does it\nconnect to, and where are credentials and transport protection weak. Nothing is rescanned: the checks that need source\ntext (disabled verification, insecure gRPC channels, risky IPC settings) run once at index time and leave facts on the\ngraph. A secret value is never stored or printed: a finding names the kind and the location, and the index keeps only a\nredacted marker for a credential literal (No secret values in the index).","md",{},true,{"title":107,"description":3143},"Hlzp0ggf5JYpCDz91VOwplC0kCvBzhCMPy_kaQ0WPgs",[3150,3152],{"title":103,"path":104,"stem":105,"description":3151,"children":-1},"The graph is one SQLite file. Node and edge details for a language live on that language's page; this page is the index of tables and kinds.",{"title":111,"path":112,"stem":113,"description":3153,"children":-1},"What .swift adds beyond Install, CLI specs, and Graph schema: heuristic vs the Swift index store, the CG_SWIFT_* flags, and the ids those pages do not spell out. Toolchain fit: cg doctor -h. Why a file stayed heuristic: cg coverage.",1792170236979]